diff options
Diffstat (limited to 'src')
74 files changed, 7558 insertions, 6000 deletions
diff --git a/src/com/android/settings/AccessibilitySettings.java b/src/com/android/settings/AccessibilitySettings.java index 6199e50..d78d2d8 100644 --- a/src/com/android/settings/AccessibilitySettings.java +++ b/src/com/android/settings/AccessibilitySettings.java @@ -19,15 +19,24 @@ package com.android.settings; import android.app.AlertDialog; import android.app.Service; import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; +import android.net.Uri; import android.os.Bundle; +import android.os.SystemProperties; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.provider.Settings; import android.text.TextUtils; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import android.view.accessibility.AccessibilityManager; import java.util.HashSet; @@ -39,14 +48,26 @@ import java.util.Map; * Activity with the accessibility settings. */ public class AccessibilitySettings extends PreferenceActivity { + private static final String DEFAULT_SCREENREADER_MARKET_LINK = + "market://search?q=pname:com.google.android.marvin.talkback"; + private final String TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX = "toggle_accessibility_service_checkbox"; private static final String ACCESSIBILITY_SERVICES_CATEGORY = "accessibility_services_category"; + private static final String POWER_BUTTON_CATEGORY = + "power_button_category"; + + private final String POWER_BUTTON_ENDS_CALL_CHECKBOX = + "power_button_ends_call"; + private CheckBoxPreference mToggleCheckBox; + private PreferenceCategory mPowerButtonCategory; + private CheckBoxPreference mPowerButtonEndsCallCheckBox; + private Map<String, ServiceInfo> mAccessibilityServices = new LinkedHashMap<String, ServiceInfo>(); @@ -63,6 +84,10 @@ public class AccessibilitySettings extends PreferenceActivity { mToggleCheckBox = (CheckBoxPreference) findPreference( TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX); + mPowerButtonCategory = (PreferenceCategory) findPreference(POWER_BUTTON_CATEGORY); + mPowerButtonEndsCallCheckBox = (CheckBoxPreference) findPreference( + POWER_BUTTON_ENDS_CALL_CHECKBOX); + addAccessibilitServicePreferences(); } @@ -105,9 +130,27 @@ public class AccessibilitySettings extends PreferenceActivity { // no service and accessibility is enabled => disable Settings.Secure.putInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 0); - setAccessibilityServicePreferencesState(false); } mToggleCheckBox.setEnabled(false); + // Notify user that they do not have any accessibility apps + // installed and direct them to Market to get TalkBack + displayNoAppsAlert(); + } + + if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)) { + int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT); + // The checkbox is labeled "Power button ends call"; thus the in-call + // Power button behavior is INCALL_POWER_BUTTON_BEHAVIOR_HANGUP if + // checked, and INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF if unchecked. + boolean powerButtonCheckboxEnabled = + (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP); + mPowerButtonEndsCallCheckBox.setChecked(powerButtonCheckboxEnabled); + mPowerButtonEndsCallCheckBox.setEnabled(true); + } else { + // No POWER key on the current device; this entire category is irrelevant. + getPreferenceScreen().removePreference(mPowerButtonCategory); } } @@ -119,7 +162,8 @@ public class AccessibilitySettings extends PreferenceActivity { } /** - * Sets the state of the preferences for enabling/disabling AccessibilityServices. + * Sets the state of the preferences for enabling/disabling + * AccessibilityServices. * * @param isEnabled If to enable or disable the preferences. */ @@ -132,9 +176,6 @@ public class AccessibilitySettings extends PreferenceActivity { for (int i = 0; i < count; i++) { Preference pref = mAccessibilityServicesCategory.getPreference(i); pref.setEnabled(isEnabled); - if (!isEnabled){ - ((CheckBoxPreference) pref).setChecked(false); - } } } @@ -145,6 +186,15 @@ public class AccessibilitySettings extends PreferenceActivity { if (TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX.equals(key)) { boolean isChecked = ((CheckBoxPreference) preference).isChecked(); handleEnableAccessibilityStateChange((CheckBoxPreference) preference); + } else if (POWER_BUTTON_ENDS_CALL_CHECKBOX.equals(key)) { + boolean isChecked = ((CheckBoxPreference) preference).isChecked(); + // The checkbox is labeled "Power button ends call"; thus the in-call + // Power button behavior is INCALL_POWER_BUTTON_BEHAVIOR_HANGUP if + // checked, and INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF if unchecked. + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, + (isChecked ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP + : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF)); } else if (preference instanceof CheckBoxPreference) { handleEnableAccessibilityServiceStateChange((CheckBoxPreference) preference); } @@ -274,4 +324,43 @@ public class AccessibilitySettings extends PreferenceActivity { mAccessibilityServicesCategory.addPreference(preference); } } + + /** + * Displays a message telling the user that they do not have any accessibility + * related apps installed and that they can get TalkBack (Google's free screen + * reader) from Market. + */ + private void displayNoAppsAlert() { + try { + PackageManager pm = getPackageManager(); + ApplicationInfo info = pm.getApplicationInfo("com.android.vending", 0); + } catch (NameNotFoundException e) { + // This is a no-op if the user does not have Android Market + return; + } + AlertDialog.Builder noAppsAlert = new AlertDialog.Builder(this); + noAppsAlert.setTitle(R.string.accessibility_service_no_apps_title); + noAppsAlert.setMessage(R.string.accessibility_service_no_apps_message); + + noAppsAlert.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String screenreaderMarketLink = + SystemProperties.get("ro.screenreader.market", + DEFAULT_SCREENREADER_MARKET_LINK); + Uri marketUri = Uri.parse(screenreaderMarketLink); + Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri); + startActivity(marketIntent); + finish(); + } + }); + + noAppsAlert.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + } + }); + + noAppsAlert.show(); + } } diff --git a/src/com/android/settings/ActivityPicker.java b/src/com/android/settings/ActivityPicker.java index 4b0469c..d984adb 100644 --- a/src/com/android/settings/ActivityPicker.java +++ b/src/com/android/settings/ActivityPicker.java @@ -16,6 +16,7 @@ package com.android.settings; +import android.graphics.ColorFilter; import android.util.DisplayMetrics; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; @@ -349,68 +350,124 @@ public class ActivityPicker extends AlertActivity implements int width = mIconWidth; int height = mIconHeight; - if (icon instanceof PaintDrawable) { - PaintDrawable painter = (PaintDrawable) icon; - painter.setIntrinsicWidth(width); - painter.setIntrinsicHeight(height); - } else if (icon instanceof BitmapDrawable) { - // Ensure the bitmap has a density. - BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; - Bitmap bitmap = bitmapDrawable.getBitmap(); - if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { - bitmapDrawable.setTargetDensity(mMetrics); - } + if (icon == null) { + return new EmptyDrawable(width, height); } - int iconWidth = icon.getIntrinsicWidth(); - int iconHeight = icon.getIntrinsicHeight(); - - if (iconWidth > 0 && iconHeight > 0) { - if (width < iconWidth || height < iconHeight) { - final float ratio = (float) iconWidth / iconHeight; - - if (iconWidth > iconHeight) { - height = (int) (width / ratio); - } else if (iconHeight > iconWidth) { - width = (int) (height * ratio); + + try { + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } else if (icon instanceof BitmapDrawable) { + // Ensure the bitmap has a density. + BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; + Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { + bitmapDrawable.setTargetDensity(mMetrics); } - - final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? - Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; - final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); - final Canvas canvas = mCanvas; - canvas.setBitmap(thumb); - // Copy the old bounds to restore them later - // If we were to do oldBounds = icon.getBounds(), - // the call to setBounds() that follows would - // change the same instance and we would lose the - // old bounds - mOldBounds.set(icon.getBounds()); - final int x = (mIconWidth - width) / 2; - final int y = (mIconHeight - height) / 2; - icon.setBounds(x, y, x + width, y + height); - icon.draw(canvas); - icon.setBounds(mOldBounds); - //noinspection deprecation - icon = new BitmapDrawable(thumb); - ((BitmapDrawable) icon).setTargetDensity(mMetrics); - } else if (iconWidth < width && iconHeight < height) { - final Bitmap.Config c = Bitmap.Config.ARGB_8888; - final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); - final Canvas canvas = mCanvas; - canvas.setBitmap(thumb); - mOldBounds.set(icon.getBounds()); - final int x = (width - iconWidth) / 2; - final int y = (height - iconHeight) / 2; - icon.setBounds(x, y, x + iconWidth, y + iconHeight); - icon.draw(canvas); - icon.setBounds(mOldBounds); - //noinspection deprecation - icon = new BitmapDrawable(thumb); - ((BitmapDrawable) icon).setTargetDensity(mMetrics); } + int iconWidth = icon.getIntrinsicWidth(); + int iconHeight = icon.getIntrinsicHeight(); + + if (iconWidth > 0 && iconHeight > 0) { + if (width < iconWidth || height < iconHeight) { + final float ratio = (float) iconWidth / iconHeight; + + if (iconWidth > iconHeight) { + height = (int) (width / ratio); + } else if (iconHeight > iconWidth) { + width = (int) (height * ratio); + } + + final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? + Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; + final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); + final Canvas canvas = mCanvas; + canvas.setBitmap(thumb); + // Copy the old bounds to restore them later + // If we were to do oldBounds = icon.getBounds(), + // the call to setBounds() that follows would + // change the same instance and we would lose the + // old bounds + mOldBounds.set(icon.getBounds()); + final int x = (mIconWidth - width) / 2; + final int y = (mIconHeight - height) / 2; + icon.setBounds(x, y, x + width, y + height); + icon.draw(canvas); + icon.setBounds(mOldBounds); + //noinspection deprecation + icon = new BitmapDrawable(thumb); + ((BitmapDrawable) icon).setTargetDensity(mMetrics); + } else if (iconWidth < width && iconHeight < height) { + final Bitmap.Config c = Bitmap.Config.ARGB_8888; + final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); + final Canvas canvas = mCanvas; + canvas.setBitmap(thumb); + mOldBounds.set(icon.getBounds()); + final int x = (width - iconWidth) / 2; + final int y = (height - iconHeight) / 2; + icon.setBounds(x, y, x + iconWidth, y + iconHeight); + icon.draw(canvas); + icon.setBounds(mOldBounds); + //noinspection deprecation + icon = new BitmapDrawable(thumb); + ((BitmapDrawable) icon).setTargetDensity(mMetrics); + } + } + + } catch (Throwable t) { + icon = new EmptyDrawable(width, height); } return icon; } } + + private static class EmptyDrawable extends Drawable { + private final int mWidth; + private final int mHeight; + + EmptyDrawable(int width, int height) { + mWidth = width; + mHeight = height; + } + + @Override + public int getIntrinsicWidth() { + return mWidth; + } + + @Override + public int getIntrinsicHeight() { + return mHeight; + } + + @Override + public int getMinimumWidth() { + return mWidth; + } + + @Override + public int getMinimumHeight() { + return mHeight; + } + + @Override + public void draw(Canvas canvas) { + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + } } diff --git a/src/com/android/settings/AppWidgetPickActivity.java b/src/com/android/settings/AppWidgetPickActivity.java index cddc687..176ac80 100644 --- a/src/com/android/settings/AppWidgetPickActivity.java +++ b/src/com/android/settings/AppWidgetPickActivity.java @@ -167,6 +167,7 @@ public class AppWidgetPickActivity extends ActivityPicker { */ void putAppWidgetItems(List<AppWidgetProviderInfo> appWidgets, List<Bundle> customExtras, List<PickAdapter.Item> items) { + if (appWidgets == null) return; final int size = appWidgets.size(); for (int i = 0; i < size; i++) { AppWidgetProviderInfo info = appWidgets.get(i); diff --git a/src/com/android/settings/ApplicationSettings.java b/src/com/android/settings/ApplicationSettings.java index 1df85dc..a919ae8 100644 --- a/src/com/android/settings/ApplicationSettings.java +++ b/src/com/android/settings/ApplicationSettings.java @@ -21,21 +21,35 @@ import android.content.DialogInterface; import android.content.res.Configuration; import android.os.Bundle; import android.preference.CheckBoxPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; +import android.preference.Preference.OnPreferenceChangeListener; import android.provider.Settings; public class ApplicationSettings extends PreferenceActivity implements DialogInterface.OnClickListener { private static final String KEY_TOGGLE_INSTALL_APPLICATIONS = "toggle_install_applications"; + private static final String KEY_APP_INSTALL_LOCATION = "app_install_location"; private static final String KEY_QUICK_LAUNCH = "quick_launch"; - private CheckBoxPreference mToggleAppInstallation; + // App installation location. Default is ask the user. + private static final int APP_INSTALL_AUTO = 0; + private static final int APP_INSTALL_DEVICE = 1; + private static final int APP_INSTALL_SDCARD = 2; - private DialogInterface mWarnInstallApps; + private static final String APP_INSTALL_DEVICE_ID = "device"; + private static final String APP_INSTALL_SDCARD_ID = "sdcard"; + private static final String APP_INSTALL_AUTO_ID = "auto"; + private CheckBoxPreference mToggleAppInstallation; + + private ListPreference mInstallLocation; + + private DialogInterface mWarnInstallApps; + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -45,6 +59,23 @@ public class ApplicationSettings extends PreferenceActivity implements mToggleAppInstallation = (CheckBoxPreference) findPreference(KEY_TOGGLE_INSTALL_APPLICATIONS); mToggleAppInstallation.setChecked(isNonMarketAppsAllowed()); + mInstallLocation = (ListPreference) findPreference(KEY_APP_INSTALL_LOCATION); + // Is app default install location set? + boolean userSetInstLocation = (Settings.System.getInt(getContentResolver(), + Settings.Secure.SET_INSTALL_LOCATION, 0) != 0); + if (!userSetInstLocation) { + getPreferenceScreen().removePreference(mInstallLocation); + } else { + mInstallLocation.setValue(getAppInstallLocation()); + mInstallLocation.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + String value = (String) newValue; + handleUpdateAppInstallLocation(value); + return false; + } + }); + } + if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) { // No hard keyboard, remove the setting for quick launch Preference quickLaunchSetting = findPreference(KEY_QUICK_LAUNCH); @@ -52,6 +83,24 @@ public class ApplicationSettings extends PreferenceActivity implements } } + protected void handleUpdateAppInstallLocation(final String value) { + if(APP_INSTALL_DEVICE_ID.equals(value)) { + Settings.System.putInt(getContentResolver(), + Settings.Secure.DEFAULT_INSTALL_LOCATION, APP_INSTALL_DEVICE); + } else if (APP_INSTALL_SDCARD_ID.equals(value)) { + Settings.System.putInt(getContentResolver(), + Settings.Secure.DEFAULT_INSTALL_LOCATION, APP_INSTALL_SDCARD); + } else if (APP_INSTALL_AUTO_ID.equals(value)) { + Settings.System.putInt(getContentResolver(), + Settings.Secure.DEFAULT_INSTALL_LOCATION, APP_INSTALL_AUTO); + } else { + // Should not happen, default to prompt... + Settings.System.putInt(getContentResolver(), + Settings.Secure.DEFAULT_INSTALL_LOCATION, APP_INSTALL_AUTO); + } + mInstallLocation.setValue(value); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -70,7 +119,7 @@ public class ApplicationSettings extends PreferenceActivity implements setNonMarketAppsAllowed(false); } } - + return super.onPreferenceTreeClick(preferenceScreen, preference); } @@ -92,6 +141,21 @@ public class ApplicationSettings extends PreferenceActivity implements Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0; } + private String getAppInstallLocation() { + int selectedLocation = Settings.System.getInt(getContentResolver(), + Settings.Secure.DEFAULT_INSTALL_LOCATION, APP_INSTALL_AUTO); + if (selectedLocation == APP_INSTALL_DEVICE) { + return APP_INSTALL_DEVICE_ID; + } else if (selectedLocation == APP_INSTALL_SDCARD) { + return APP_INSTALL_SDCARD_ID; + } else if (selectedLocation == APP_INSTALL_AUTO) { + return APP_INSTALL_AUTO_ID; + } else { + // Default value, should not happen. + return APP_INSTALL_AUTO_ID; + } + } + private void warnAppInstallation() { mWarnInstallApps = new AlertDialog.Builder(this) .setTitle(getString(R.string.error_title)) @@ -101,6 +165,4 @@ public class ApplicationSettings extends PreferenceActivity implements .setNegativeButton(android.R.string.no, null) .show(); } - - } diff --git a/src/com/android/settings/BandMode.java b/src/com/android/settings/BandMode.java index a8c7833..4c7663e 100644 --- a/src/com/android/settings/BandMode.java +++ b/src/com/android/settings/BandMode.java @@ -62,7 +62,7 @@ public class BandMode extends Activity { setContentView(R.layout.band_mode); setTitle(getString(R.string.band_mode_title)); - getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT, + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); mPhone = PhoneFactory.getDefaultPhone(); diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java index fe3b997..7494e2f 100644 --- a/src/com/android/settings/BrightnessPreference.java +++ b/src/com/android/settings/BrightnessPreference.java @@ -55,6 +55,7 @@ public class BrightnessPreference extends SeekBarPreference implements com.android.internal.R.bool.config_automatic_brightness_available); setDialogLayoutResource(R.layout.preference_dialog_brightness); + setDialogIcon(R.drawable.ic_settings_display); } @Override diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java new file mode 100644 index 0000000..0672ad9 --- /dev/null +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -0,0 +1,198 @@ +/* + * 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 com.android.internal.widget.LockPatternUtils; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.preference.PreferenceScreen; + +public class ChooseLockGeneric extends PreferenceActivity { + private static final int MIN_PASSWORD_LENGTH = 4; + private static final String KEY_UNLOCK_SET_NONE = "unlock_set_none"; + private static final String KEY_UNLOCK_SET_PIN = "unlock_set_pin"; + private static final String KEY_UNLOCK_SET_PASSWORD = "unlock_set_password"; + private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern"; + private static final int CONFIRM_EXISTING_REQUEST = 100; + private static final String PASSWORD_CONFIRMED = "password_confirmed"; + private static final String CONFIRM_CREDENTIALS = "confirm_credentials"; + + private ChooseLockSettingsHelper mChooseLockSettingsHelper; + private DevicePolicyManager mDPM; + private boolean mPasswordConfirmed = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); + mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this); + + if (savedInstanceState != null) { + mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED); + } + + if (!mPasswordConfirmed) { + ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this); + if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) { + mPasswordConfirmed = true; // no password set, so no need to confirm + updatePreferencesOrFinish(); + } + } else { + updatePreferencesOrFinish(); + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, + Preference preference) { + final String key = preference.getKey(); + boolean handled = true; + if (KEY_UNLOCK_SET_NONE.equals(key)) { + updateUnlockMethodAndFinish(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); + } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) { + updateUnlockMethodAndFinish(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); + } else if (KEY_UNLOCK_SET_PIN.equals(key)) { + updateUnlockMethodAndFinish(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC); + } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) { + updateUnlockMethodAndFinish(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC); + } else { + handled = false; + } + return handled; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == RESULT_OK) { + mPasswordConfirmed = true; + updatePreferencesOrFinish(); + } else { + setResult(RESULT_CANCELED); + finish(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // Saved so we don't force user to re-enter their password if configuration changes + outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed); + } + + private void updatePreferencesOrFinish() { + int quality = getIntent().getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1); + if (quality == -1) { + // If caller didn't specify password quality, show the UI and allow the user to choose. + quality = mChooseLockSettingsHelper.utils().getKeyguardStoredPasswordQuality(); + final PreferenceScreen prefScreen = getPreferenceScreen(); + if (prefScreen != null) { + prefScreen.removeAll(); + } + addPreferencesFromResource(R.xml.security_settings_picker); + disableUnusablePreferences(mDPM.getPasswordQuality(null)); + } else { + updateUnlockMethodAndFinish(quality); + } + } + + /*** + * Disables preferences that are less secure than required quality. + * + * @param quality the requested quality. + */ + private void disableUnusablePreferences(final int quality) { + final Preference picker = getPreferenceScreen().findPreference("security_picker_category"); + final PreferenceCategory cat = (PreferenceCategory) picker; + final int preferenceCount = cat.getPreferenceCount(); + for (int i = 0; i < preferenceCount; i++) { + Preference pref = cat.getPreference(i); + if (pref instanceof PreferenceScreen) { + final String key = ((PreferenceScreen) pref).getKey(); + boolean enabled = true; + if (KEY_UNLOCK_SET_NONE.equals(key)) { + enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) { + enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; + } else if (KEY_UNLOCK_SET_PIN.equals(key)) { + enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) { + enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; + } + if (!enabled) { + pref.setSummary(R.string.unlock_set_unlock_disabled_summary); + pref.setEnabled(false); + } + } + } + } + + /** + * Invokes an activity to change the user's pattern, password or PIN based on given quality + * and minimum quality specified by DevicePolicyManager. If quality is + * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, password is cleared. + * + * @param quality the desired quality. Ignored if DevicePolicyManager requires more security. + */ + void updateUnlockMethodAndFinish(int quality) { + // Sanity check. We should never get here without confirming user's existing password first. + if (!mPasswordConfirmed) { + throw new IllegalStateException("Tried to update password without confirming first"); + } + + // Compare minimum allowed password quality and launch appropriate security setting method + int minQuality = mDPM.getPasswordQuality(null); + if (quality < minQuality) { + quality = minQuality; + } + if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) { + int minLength = mDPM.getPasswordMinimumLength(null); + if (minLength < MIN_PASSWORD_LENGTH) { + minLength = MIN_PASSWORD_LENGTH; + } + final int maxLength = mDPM.getPasswordMaximumLength(quality); + Intent intent = new Intent().setClass(this, ChooseLockPassword.class); + intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality); + intent.putExtra(ChooseLockPassword.PASSWORD_MIN_KEY, minLength); + intent.putExtra(ChooseLockPassword.PASSWORD_MAX_KEY, maxLength); + intent.putExtra(CONFIRM_CREDENTIALS, false); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); + } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) { + boolean showTutorial = !mChooseLockSettingsHelper.utils().isPatternEverChosen(); + Intent intent = new Intent(); + intent.setClass(this, showTutorial + ? ChooseLockPatternTutorial.class + : ChooseLockPattern.class); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + intent.putExtra("key_lock_method", "pattern"); + intent.putExtra(CONFIRM_CREDENTIALS, false); + startActivity(intent); + } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { + mChooseLockSettingsHelper.utils().clearLock(); + setResult(RESULT_OK); + } + finish(); + } +} diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java new file mode 100644 index 0000000..b5e72d7 --- /dev/null +++ b/src/com/android/settings/ChooseLockPassword.java @@ -0,0 +1,360 @@ +/* + * 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 com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.PasswordEntryKeyboardHelper; +import com.android.internal.widget.PasswordEntryKeyboardView; + +import android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.content.Intent; +import android.inputmethodservice.KeyboardView; +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.Selection; +import android.text.Spannable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + + +public class ChooseLockPassword extends Activity implements OnClickListener, OnEditorActionListener, + TextWatcher { + private static final String KEY_FIRST_PIN = "first_pin"; + private static final String KEY_UI_STAGE = "ui_stage"; + private TextView mPasswordEntry; + private int mPasswordMinLength = 4; + private int mPasswordMaxLength = 16; + private LockPatternUtils mLockPatternUtils; + private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; + private ChooseLockSettingsHelper mChooseLockSettingsHelper; + private com.android.settings.ChooseLockPassword.Stage mUiStage = Stage.Introduction; + private TextView mHeaderText; + private String mFirstPin; + private KeyboardView mKeyboardView; + private PasswordEntryKeyboardHelper mKeyboardHelper; + private boolean mIsAlphaMode; + private Button mCancelButton; + private Button mNextButton; + public static final String PASSWORD_MIN_KEY = "lockscreen.password_min"; + public static final String PASSWORD_MAX_KEY = "lockscreen.password_max"; + private static Handler mHandler = new Handler(); + private static final int CONFIRM_EXISTING_REQUEST = 58; + static final int RESULT_FINISHED = RESULT_FIRST_USER; + private static final long ERROR_MESSAGE_TIMEOUT = 3000; + + /** + * Keep track internally of where the user is in choosing a pattern. + */ + protected enum Stage { + + Introduction(R.string.lockpassword_choose_your_password_header, + R.string.lockpassword_choose_your_pin_header, + R.string.lockpassword_continue_label), + + NeedToConfirm(R.string.lockpassword_confirm_your_password_header, + R.string.lockpassword_confirm_your_pin_header, + R.string.lockpassword_ok_label), + + ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match, + R.string.lockpassword_confirm_pins_dont_match, + R.string.lockpassword_continue_label); + + /** + * @param headerMessage The message displayed at the top. + */ + Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) { + this.alphaHint = hintInAlpha; + this.numericHint = hintInNumeric; + this.buttonText = nextButtonText; + } + + public final int alphaHint; + public final int numericHint; + public final int buttonText; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mLockPatternUtils = new LockPatternUtils(this); + mRequestedQuality = getIntent().getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedQuality); + mPasswordMinLength = getIntent().getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength); + mPasswordMaxLength = getIntent().getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength); + + final boolean confirmCredentials = getIntent().getBooleanExtra("confirm_credentials", true); + int minMode = mLockPatternUtils.getRequestedPasswordQuality(); + if (mRequestedQuality < minMode) { + mRequestedQuality = minMode; + } + int minLength = mLockPatternUtils.getRequestedMinimumPasswordLength(); + if (mPasswordMinLength < minLength) { + mPasswordMinLength = minLength; + } + initViews(); + mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this); + if (savedInstanceState == null) { + updateStage(Stage.Introduction); + if (confirmCredentials) { + mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, + null, null); + } + } + } + + private void initViews() { + setContentView(R.layout.choose_lock_password); + // Disable IME on our window since we provide our own keyboard + getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + + mCancelButton = (Button) findViewById(R.id.cancel_button); + mCancelButton.setOnClickListener(this); + mNextButton = (Button) findViewById(R.id.next_button); + mNextButton.setOnClickListener(this); + + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mPasswordEntry = (TextView) findViewById(R.id.password_entry); + mPasswordEntry.setOnEditorActionListener(this); + mPasswordEntry.addTextChangedListener(this); + + mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality + || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality; + mKeyboardHelper = new PasswordEntryKeyboardHelper(this, mKeyboardView, mPasswordEntry); + mKeyboardHelper.setKeyboardMode(mIsAlphaMode ? + PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA + : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + + mHeaderText = (TextView) findViewById(R.id.headerText); + mKeyboardView.requestFocus(); + } + + @Override + protected void onResume() { + super.onResume(); + updateStage(mUiStage); + mKeyboardView.requestFocus(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(KEY_UI_STAGE, mUiStage.name()); + outState.putString(KEY_FIRST_PIN, mFirstPin); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + String state = savedInstanceState.getString(KEY_UI_STAGE); + mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN); + if (state != null) { + mUiStage = Stage.valueOf(state); + updateStage(mUiStage); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case CONFIRM_EXISTING_REQUEST: + if (resultCode != Activity.RESULT_OK) { + setResult(RESULT_FINISHED); + finish(); + } + break; + } + } + + protected void updateStage(Stage stage) { + mUiStage = stage; + updateUi(); + } + + /** + * Validates PIN and returns a message to display if PIN fails test. + * @param password the raw password the user typed in + * @return error message to show to user or null if password is OK + */ + private String validatePassword(String password) { + if (password.length() < mPasswordMinLength) { + return getString(mIsAlphaMode ? + R.string.lockpassword_password_too_short + : R.string.lockpassword_pin_too_short, mPasswordMinLength); + } + if (password.length() > mPasswordMaxLength) { + return getString(mIsAlphaMode ? + R.string.lockpassword_password_too_long + : R.string.lockpassword_pin_too_long, mPasswordMaxLength); + } + boolean hasAlpha = false; + boolean hasDigit = false; + boolean hasSymbol = false; + for (int i = 0; i < password.length(); i++) { + char c = password.charAt(i); + // allow non white space Latin-1 characters only + if (c <= 32 || c > 127) { + return getString(R.string.lockpassword_illegal_character); + } + if (c >= '0' && c <= '9') { + hasDigit = true; + } else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { + hasAlpha = true; + } else { + hasSymbol = true; + } + } + if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality + && (hasAlpha | hasSymbol)) { + // This shouldn't be possible unless user finds some way to bring up soft keyboard + return getString(R.string.lockpassword_pin_contains_non_digits); + } else { + final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC + == mRequestedQuality; + final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC + == mRequestedQuality; + final boolean symbolic = false; // not yet + if ((alphabetic || alphanumeric) && !hasAlpha) { + return getString(R.string.lockpassword_password_requires_alpha); + } + if (alphanumeric && !hasDigit) { + return getString(R.string.lockpassword_password_requires_digit); + } + if (symbolic && !hasSymbol) { + return getString(R.string.lockpassword_password_requires_symbol); + } + } + return null; + } + + private void handleNext() { + final String pin = mPasswordEntry.getText().toString(); + if (TextUtils.isEmpty(pin)) { + return; + } + String errorMsg = null; + if (mUiStage == Stage.Introduction) { + errorMsg = validatePassword(pin); + if (errorMsg == null) { + mFirstPin = pin; + updateStage(Stage.NeedToConfirm); + mPasswordEntry.setText(""); + } + } else if (mUiStage == Stage.NeedToConfirm) { + if (mFirstPin.equals(pin)) { + mLockPatternUtils.clearLock(); + mLockPatternUtils.saveLockPassword(pin, mRequestedQuality); + finish(); + } else { + updateStage(Stage.ConfirmWrong); + CharSequence tmp = mPasswordEntry.getText(); + if (tmp != null) { + Selection.setSelection((Spannable) tmp, 0, tmp.length()); + } + } + } + if (errorMsg != null) { + showError(errorMsg, mUiStage); + } + } + + public void onClick(View v) { + switch (v.getId()) { + case R.id.next_button: + handleNext(); + break; + + case R.id.cancel_button: + finish(); + break; + } + } + + private void showError(String msg, final Stage next) { + mHeaderText.setText(msg); + mHandler.postDelayed(new Runnable() { + public void run() { + updateStage(next); + } + }, ERROR_MESSAGE_TIMEOUT); + } + + 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) { + handleNext(); + return true; + } + return false; + } + + /** + * Update the hint based on current Stage and length of password entry + */ + private void updateUi() { + String password = mPasswordEntry.getText().toString(); + final int length = password.length(); + if (mUiStage == Stage.Introduction && length > 0) { + if (length < mPasswordMinLength) { + String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short + : R.string.lockpassword_pin_too_short, mPasswordMinLength); + mHeaderText.setText(msg); + mNextButton.setEnabled(false); + } else { + String error = validatePassword(password); + if (error != null) { + mHeaderText.setText(error); + mNextButton.setEnabled(false); + } else { + mHeaderText.setText(R.string.lockpassword_press_continue); + mNextButton.setEnabled(true); + } + } + } else { + mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint); + mNextButton.setEnabled(length > 0); + } + mNextButton.setText(mUiStage.buttonText); + } + + public void afterTextChanged(Editable s) { + // Changing the text while error displayed resets to NeedToConfirm state + if (mUiStage == Stage.ConfirmWrong) { + mUiStage = Stage.NeedToConfirm; + } + updateUi(); + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } +} diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java index f103c6b..b5c0e80 100644 --- a/src/com/android/settings/ChooseLockPattern.java +++ b/src/com/android/settings/ChooseLockPattern.java @@ -45,7 +45,6 @@ import java.util.List; * - saves chosen password when confirmed */ public class ChooseLockPattern extends Activity implements View.OnClickListener{ - /** * Used by the choose lock pattern wizard to indicate the wizard is * finished, and each activity in the wizard should finish. @@ -56,7 +55,9 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ * result. */ static final int RESULT_FINISHED = RESULT_FIRST_USER; - + + public static final int CONFIRM_EXISTING_REQUEST = 55; + // how long after a confirmation message is shown before moving on static final int INFORMATION_MSG_TIMEOUT_MS = 3000; @@ -65,29 +66,38 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ private static final int ID_EMPTY_MESSAGE = -1; - protected TextView mHeaderText; protected LockPatternView mLockPatternView; protected TextView mFooterText; private TextView mFooterLeftButton; private TextView mFooterRightButton; - protected List<LockPatternView.Cell> mChosenPattern = null; - protected LockPatternUtils mLockPatternUtils; - /** * The patten used during the help screen to show how to draw a pattern. */ private final List<LockPatternView.Cell> mAnimatePattern = - Collections.unmodifiableList( - Lists.newArrayList( - LockPatternView.Cell.of(0, 0), - LockPatternView.Cell.of(0, 1), - LockPatternView.Cell.of(1, 1), - LockPatternView.Cell.of(2, 1) - )); + Collections.unmodifiableList(Lists.newArrayList( + LockPatternView.Cell.of(0, 0), + LockPatternView.Cell.of(0, 1), + LockPatternView.Cell.of(1, 1), + LockPatternView.Cell.of(2, 1) + )); + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case CONFIRM_EXISTING_REQUEST: + if (resultCode != Activity.RESULT_OK) { + setResult(RESULT_FINISHED); + finish(); + } + updateStage(Stage.Introduction); + break; + } + } /** * The pattern listener that responds according to a user choosing a new @@ -125,7 +135,7 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ } } - public void onPatternCellAdded(List<Cell> pattern) { + public void onPatternCellAdded(List<Cell> pattern) { } @@ -250,19 +260,19 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ } }; + private ChooseLockSettingsHelper mChooseLockSettingsHelper; + private static final String KEY_UI_STAGE = "uiStage"; private static final String KEY_PATTERN_CHOICE = "chosenPattern"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - mLockPatternUtils = new LockPatternUtils(getContentResolver()); - + mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this); requestWindowFeature(Window.FEATURE_NO_TITLE); setupViews(); - + // make it so unhandled touch events within the unlock screen go to the // lock pattern view. final LinearLayoutWithDefaultTouchRecepient topLayout @@ -270,12 +280,22 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ R.id.topLayout); topLayout.setDefaultTouchRecepient(mLockPatternView); + final boolean confirmCredentials = getIntent().getBooleanExtra("confirm_credentials", true); + if (savedInstanceState == null) { - // first launch - updateStage(Stage.Introduction); - if (mLockPatternUtils.savedPatternExists()) { - confirmPattern(); - } + if (confirmCredentials) { + // first launch. As a security measure, we're in NeedToConfirm mode until we know + // there isn't an existing password or the user confirms their password. + updateStage(Stage.NeedToConfirm); + boolean launchedConfirmationActivity = + mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, + null, null); + if (!launchedConfirmationActivity) { + updateStage(Stage.Introduction); + } + } else { + updateStage(Stage.Introduction); + } } else { // restore from previous state final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE); @@ -285,19 +305,20 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]); } } - + /** * Keep all "find view" related stuff confined to this function since in * case someone needs to subclass and customize. */ protected void setupViews() { setContentView(R.layout.choose_lock_pattern); - + mHeaderText = (TextView) findViewById(R.id.headerText); mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern); mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener); - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + mLockPatternView.setTactileFeedbackEnabled( + mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled()); mFooterText = (TextView) findViewById(R.id.footerText); @@ -364,35 +385,6 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ return super.onKeyDown(keyCode, event); } - /** - * Launch screen to confirm the existing lock pattern. - * @see #onActivityResult(int, int, android.content.Intent) - */ - protected void confirmPattern() { - final Intent intent = new Intent(); - intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern"); - startActivityForResult(intent, 55); - } - - /** - * @see #confirmPattern - */ - @Override - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode != 55) { - return; - } - - if (resultCode != Activity.RESULT_OK) { - setResult(RESULT_FINISHED); - finish(); - } - updateStage(Stage.Introduction); - } - @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -414,7 +406,7 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ mUiStage = stage; - // header text, footer text, visibility and + // header text, footer text, visibility and // enabled state all known from the stage if (stage == Stage.ChoiceTooShort) { mHeaderText.setText( @@ -486,16 +478,17 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{ } private void saveChosenPatternAndFinish() { - final boolean lockVirgin = !mLockPatternUtils.isPatternEverChosen(); + LockPatternUtils utils = mChooseLockSettingsHelper.utils(); + final boolean lockVirgin = !utils.isPatternEverChosen(); - mLockPatternUtils.saveLockPattern(mChosenPattern); - mLockPatternUtils.setLockPatternEnabled(true); + utils.saveLockPattern(mChosenPattern); + utils.setLockPatternEnabled(true); if (lockVirgin) { - mLockPatternUtils.setVisiblePatternEnabled(true); - mLockPatternUtils.setTactileFeedbackEnabled(false); + utils.setVisiblePatternEnabled(true); + utils.setTactileFeedbackEnabled(false); } - + setResult(RESULT_FINISHED); finish(); } diff --git a/src/com/android/settings/ChooseLockPatternExample.java b/src/com/android/settings/ChooseLockPatternExample.java index 77517b9..cba88b0 100644 --- a/src/com/android/settings/ChooseLockPatternExample.java +++ b/src/com/android/settings/ChooseLockPatternExample.java @@ -25,7 +25,6 @@ import android.view.View; import android.widget.ImageView; public class ChooseLockPatternExample extends Activity implements View.OnClickListener { - private static final int REQUESTCODE_CHOOSE = 1; private static final long START_DELAY = 1000; protected static final String TAG = "Settings"; private View mNextButton; @@ -38,26 +37,26 @@ public class ChooseLockPatternExample extends Activity implements View.OnClickLi startAnimation(mAnimation); } }; - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.choose_lock_pattern_example); initViews(); } - + @Override protected void onResume() { super.onResume(); mHandler.postDelayed(mRunnable, START_DELAY); } - + @Override protected void onPause() { super.onPause(); stopAnimation(mAnimation); } - + public void onClick(View v) { if (v == mSkipButton) { // Canceling, so finish all @@ -66,37 +65,31 @@ public class ChooseLockPatternExample extends Activity implements View.OnClickLi } else if (v == mNextButton) { stopAnimation(mAnimation); Intent intent = new Intent(this, ChooseLockPattern.class); - startActivityForResult(intent, REQUESTCODE_CHOOSE); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUESTCODE_CHOOSE && resultCode == ChooseLockPattern.RESULT_FINISHED) { - setResult(resultCode); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); finish(); } } - + private void initViews() { mNextButton = findViewById(R.id.next_button); mNextButton.setOnClickListener(this); - + mSkipButton = findViewById(R.id.skip_button); mSkipButton.setOnClickListener(this); - + mImageView = (ImageView) findViewById(R.id.lock_anim); mImageView.setBackgroundResource(R.drawable.lock_anim); mImageView.setOnClickListener(this); mAnimation = (AnimationDrawable) mImageView.getBackground(); } - + protected void startAnimation(final AnimationDrawable animation) { if (animation != null && !animation.isRunning()) { animation.run(); } } - + protected void stopAnimation(final AnimationDrawable animation) { if (animation != null && animation.isRunning()) animation.stop(); } diff --git a/src/com/android/settings/ChooseLockPatternTutorial.java b/src/com/android/settings/ChooseLockPatternTutorial.java index 6e92ca8..ee0019f 100644 --- a/src/com/android/settings/ChooseLockPatternTutorial.java +++ b/src/com/android/settings/ChooseLockPatternTutorial.java @@ -24,26 +24,24 @@ import android.os.Bundle; import android.view.View; public class ChooseLockPatternTutorial extends Activity implements View.OnClickListener { - private static final int REQUESTCODE_EXAMPLE = 1; - private View mNextButton; private View mSkipButton; - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Don't show the tutorial if the user has seen it before. - LockPatternUtils lockPatternUtils = new LockPatternUtils(getContentResolver()); + LockPatternUtils lockPatternUtils = new LockPatternUtils(this); if (savedInstanceState == null && lockPatternUtils.isPatternEverChosen()) { - Intent intent = new Intent(); - intent.setClassName("com.android.settings", "com.android.settings.ChooseLockPattern"); + Intent intent = new Intent(this, ChooseLockPattern.class); + intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); startActivity(intent); finish(); } else { initViews(); } } - + private void initViews() { setContentView(R.layout.choose_lock_pattern_tutorial); mNextButton = findViewById(R.id.next_button); @@ -58,18 +56,11 @@ public class ChooseLockPatternTutorial extends Activity implements View.OnClickL setResult(ChooseLockPattern.RESULT_FINISHED); finish(); } else if (v == mNextButton) { - startActivityForResult(new Intent(this, ChooseLockPatternExample.class), - REQUESTCODE_EXAMPLE); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUESTCODE_EXAMPLE && resultCode == ChooseLockPattern.RESULT_FINISHED) { - setResult(resultCode); + Intent intent = new Intent(this, ChooseLockPatternExample.class); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); finish(); } } - } diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java new file mode 100644 index 0000000..ba83f8e --- /dev/null +++ b/src/com/android/settings/ChooseLockSettingsHelper.java @@ -0,0 +1,96 @@ +/* + * 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 android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.content.Intent; + +import com.android.internal.widget.LockPatternUtils; + +public class ChooseLockSettingsHelper { + private LockPatternUtils mLockPatternUtils; + private Activity mActivity; + + public ChooseLockSettingsHelper(Activity activity) { + mActivity = activity; + mLockPatternUtils = new LockPatternUtils(activity); + } + + public LockPatternUtils utils() { + return mLockPatternUtils; + } + + /** + * If a pattern, password or PIN exists, prompt the user before allowing them to change it. + * @param message optional message to display about the action about to be done + * @param details optional detail message to display + * @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 launched = false; + switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) { + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + launched = confirmPattern(request, message, details); + break; + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + // TODO: update UI layout for ConfirmPassword to show message and details + launched = confirmPassword(request); + break; + } + return launched; + } + + /** + * Launch screen to confirm the existing lock pattern. + * @param message shown in header of ConfirmLockPattern if not null + * @param details shown in footer of ConfirmLockPattern if not null + * @see #onActivityResult(int, int, android.content.Intent) + * @return true if we launched an activity to confirm pattern + */ + private boolean confirmPattern(int request, CharSequence message, CharSequence details) { + if (!mLockPatternUtils.isLockPatternEnabled() || !mLockPatternUtils.savedPatternExists()) { + return false; + } + final Intent intent = new Intent(); + // supply header and footer text in the intent + intent.putExtra(ConfirmLockPattern.HEADER_TEXT, message); + intent.putExtra(ConfirmLockPattern.FOOTER_TEXT, details); + intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern"); + mActivity.startActivityForResult(intent, request); + return true; + } + + /** + * Launch screen to confirm the existing lock password. + * @see #onActivityResult(int, int, android.content.Intent) + * @return true if we launched an activity to confirm password + */ + private boolean confirmPassword(int request) { + if (!mLockPatternUtils.isLockPasswordEnabled()) return false; + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPassword"); + mActivity.startActivityForResult(intent, request); + return true; + } + + +} diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java new file mode 100644 index 0000000..6bc135b --- /dev/null +++ b/src/com/android/settings/ConfirmLockPassword.java @@ -0,0 +1,131 @@ +/* + * 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 com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.PasswordEntryKeyboardHelper; +import com.android.internal.widget.PasswordEntryKeyboardView; + +import android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +public class ConfirmLockPassword extends Activity implements OnClickListener, + OnEditorActionListener { + private static final long ERROR_MESSAGE_TIMEOUT = 3000; + private TextView mPasswordEntry; + private LockPatternUtils mLockPatternUtils; + private TextView mHeaderText; + private Handler mHandler = new Handler(); + private PasswordEntryKeyboardHelper mKeyboardHelper; + private PasswordEntryKeyboardView mKeyboardView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mLockPatternUtils = new LockPatternUtils(this); + initViews(); + } + + private void initViews() { + final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality(); + setContentView(R.layout.confirm_lock_password); + // Disable IME on our window since we provide our own keyboard + getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + + findViewById(R.id.cancel_button).setOnClickListener(this); + findViewById(R.id.next_button).setOnClickListener(this); + mPasswordEntry = (TextView) findViewById(R.id.password_entry); + mPasswordEntry.setOnEditorActionListener(this); + mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); + mHeaderText = (TextView) findViewById(R.id.headerText); + final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality + || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality; + mHeaderText.setText(isAlpha ? R.string.lockpassword_confirm_your_password_header + : R.string.lockpassword_confirm_your_pin_header); + mKeyboardHelper = new PasswordEntryKeyboardHelper(this, mKeyboardView, mPasswordEntry); + mKeyboardHelper.setKeyboardMode(isAlpha ? PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA + : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); + mKeyboardView.requestFocus(); + } + + @Override + protected void onPause() { + super.onPause(); + mKeyboardView.requestFocus(); + } + + @Override + protected void onResume() { + // TODO Auto-generated method stub + super.onResume(); + mKeyboardView.requestFocus(); + } + + private void handleNext() { + final String pin = mPasswordEntry.getText().toString(); + if (mLockPatternUtils.checkPassword(pin)) { + setResult(RESULT_OK); + finish(); + } else { + showError(R.string.lockpattern_need_to_unlock_wrong); + } + } + + public void onClick(View v) { + switch (v.getId()) { + case R.id.next_button: + handleNext(); + break; + + case R.id.cancel_button: + setResult(RESULT_CANCELED); + finish(); + break; + } + } + + private void showError(int msg) { + mHeaderText.setText(msg); + mPasswordEntry.setText(null); + mHandler.postDelayed(new Runnable() { + public void run() { + mHeaderText.setText(R.string.lockpassword_confirm_your_password_header); + } + }, ERROR_MESSAGE_TIMEOUT); + } + + 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) { + handleNext(); + return true; + } + return false; + } +} diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java index a91b45f..eb9a4d8 100644 --- a/src/com/android/settings/ConfirmLockPattern.java +++ b/src/com/android/settings/ConfirmLockPattern.java @@ -80,7 +80,7 @@ public class ConfirmLockPattern extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mLockPatternUtils = new LockPatternUtils(getContentResolver()); + mLockPatternUtils = new LockPatternUtils(this); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.confirm_lock_pattern); @@ -94,7 +94,7 @@ public class ConfirmLockPattern extends Activity { final LinearLayoutWithDefaultTouchRecepient topLayout = (LinearLayoutWithDefaultTouchRecepient) findViewById( R.id.topLayout); - topLayout.setDefaultTouchRecepient(mLockPatternView); + topLayout.setDefaultTouchRecepient(mLockPatternView); Intent intent = getIntent(); if (intent != null) { @@ -161,7 +161,7 @@ public class ConfirmLockPattern extends Activity { } else { mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer); } - + mLockPatternView.setEnabled(true); mLockPatternView.enableInput(); break; @@ -176,7 +176,7 @@ public class ConfirmLockPattern extends Activity { } else { mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer); } - + mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); mLockPatternView.setEnabled(true); mLockPatternView.enableInput(); diff --git a/src/com/android/settings/CredentialInstaller.java b/src/com/android/settings/CredentialInstaller.java index 5a457d7..7c63b1c 100644 --- a/src/com/android/settings/CredentialInstaller.java +++ b/src/com/android/settings/CredentialInstaller.java @@ -32,6 +32,7 @@ import android.util.Log; */ public class CredentialInstaller extends Activity { private static final String TAG = "CredentialInstaller"; + private static final String UNLOCKING = "ulck"; private KeyStore mKeyStore = KeyStore.getInstance(); private boolean mUnlocking = false; @@ -42,15 +43,26 @@ public class CredentialInstaller extends Activity { if (!"com.android.certinstaller".equals(getCallingPackage())) finish(); - if (!isKeyStoreLocked()) { + if (isKeyStoreUnlocked()) { install(); - finish(); } else if (!mUnlocking) { mUnlocking = true; Credentials.getInstance().unlock(this); - } else { - finish(); + return; } + finish(); + } + + @Override + protected void onSaveInstanceState(Bundle outStates) { + super.onSaveInstanceState(outStates); + outStates.putBoolean(UNLOCKING, mUnlocking); + } + + @Override + protected void onRestoreInstanceState(Bundle savedStates) { + super.onRestoreInstanceState(savedStates); + mUnlocking = savedStates.getBoolean(UNLOCKING); } private void install() { @@ -61,13 +73,13 @@ public class CredentialInstaller extends Activity { byte[] data = bundle.getByteArray(key); if (data == null) continue; boolean success = mKeyStore.put(key.getBytes(), data); - Log.v(TAG, "install " + key + ": " + data.length + " success? " + success); + Log.d(TAG, "install " + key + ": " + data.length + " success? " + success); if (!success) return; } setResult(RESULT_OK); } - private boolean isKeyStoreLocked() { - return (mKeyStore.test() != KeyStore.NO_ERROR); + private boolean isKeyStoreUnlocked() { + return (mKeyStore.test() == KeyStore.NO_ERROR); } } diff --git a/src/com/android/settings/DeviceAdminAdd.java b/src/com/android/settings/DeviceAdminAdd.java new file mode 100644 index 0000000..4760277 --- /dev/null +++ b/src/com/android/settings/DeviceAdminAdd.java @@ -0,0 +1,284 @@ +/* + * 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 org.xmlpull.v1.XmlPullParserException; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.admin.DeviceAdminInfo; +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +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.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteCallback; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AppSecurityPermissions; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import java.io.IOException; +import java.util.ArrayList; + +public class DeviceAdminAdd extends Activity { + static final String TAG = "DeviceAdminAdd"; + + static final int DIALOG_WARNING = 1; + + Handler mHandler; + + DevicePolicyManager mDPM; + DeviceAdminInfo mDeviceAdmin; + CharSequence mAddMsgText; + + TextView mTitle; + ImageView mAdminIcon; + TextView mAdminName; + TextView mAdminDescription; + TextView mAddMsg; + TextView mAdminWarning; + ViewGroup mAdminPolicies; + Button mActionButton; + Button mCancelButton; + + View mSelectLayout; + + final ArrayList<View> mAddingPolicies = new ArrayList<View>(); + final ArrayList<View> mActivePolicies = new ArrayList<View>(); + + boolean mAdding; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mHandler = new Handler(getMainLooper()); + + mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE); + + if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + Log.w(TAG, "Can now start ADD_DEVICE_ADMIN as a new task"); + finish(); + return; + } + + ComponentName cn = (ComponentName)getIntent().getParcelableExtra( + DevicePolicyManager.EXTRA_DEVICE_ADMIN); + if (cn == null) { + Log.w(TAG, "No component specified in " + getIntent().getAction()); + finish(); + return; + } + if (DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN.equals(getIntent().getAction())) { + // If this was an add request, then just exit immediately if the + // given component is already added. + if (mDPM.isAdminActive(cn)) { + setResult(Activity.RESULT_OK); + finish(); + return; + } + } + + ActivityInfo ai; + try { + ai = getPackageManager().getReceiverInfo(cn, + PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to retrieve device policy " + cn, e); + finish(); + return; + } + + ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + try { + mDeviceAdmin= new DeviceAdminInfo(this, ri); + } catch (XmlPullParserException e) { + Log.w(TAG, "Unable to retrieve device policy " + cn, e); + finish(); + return; + } catch (IOException e) { + Log.w(TAG, "Unable to retrieve device policy " + cn, e); + finish(); + return; + } + + mAddMsgText = getIntent().getCharSequenceExtra( + DevicePolicyManager.EXTRA_ADD_EXPLANATION); + + setContentView(R.layout.device_admin_add); + + mTitle = (TextView)findViewById(R.id.title); + mAdminIcon = (ImageView)findViewById(R.id.admin_icon); + mAdminName = (TextView)findViewById(R.id.admin_name); + mAdminDescription = (TextView)findViewById(R.id.admin_description); + mAddMsg = (TextView)findViewById(R.id.add_msg); + mAdminWarning = (TextView)findViewById(R.id.admin_warning); + mAdminPolicies = (ViewGroup)findViewById(R.id.admin_policies); + mCancelButton = (Button)findViewById(R.id.cancel_button); + mCancelButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + finish(); + } + }); + mActionButton = (Button)findViewById(R.id.action_button); + mActionButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (mAdding) { + try { + mDPM.setActiveAdmin(mDeviceAdmin.getComponent()); + setResult(Activity.RESULT_OK); + } catch (RuntimeException e) { + // Something bad happened... could be that it was + // already set, though. + Log.w(TAG, "Exception trying to activate admin " + + mDeviceAdmin.getComponent(), e); + if (mDPM.isAdminActive(mDeviceAdmin.getComponent())) { + setResult(Activity.RESULT_OK); + } + } + finish(); + } else { + mDPM.getRemoveWarning(mDeviceAdmin.getComponent(), + new RemoteCallback(mHandler) { + @Override + protected void onResult(Bundle bundle) { + CharSequence msg = bundle != null + ? bundle.getCharSequence( + DeviceAdminReceiver.EXTRA_DISABLE_WARNING) + : null; + if (msg == null) { + mDPM.removeActiveAdmin(mDeviceAdmin.getComponent()); + finish(); + } else { + Bundle args = new Bundle(); + args.putCharSequence( + DeviceAdminReceiver.EXTRA_DISABLE_WARNING, msg); + showDialog(DIALOG_WARNING, args); + } + } + }); + } + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + updateInterface(); + } + + @Override + protected Dialog onCreateDialog(int id, Bundle args) { + switch (id) { + case DIALOG_WARNING: { + CharSequence msg = args.getCharSequence(DeviceAdminReceiver.EXTRA_DISABLE_WARNING); + AlertDialog.Builder builder = new AlertDialog.Builder( + DeviceAdminAdd.this); + builder.setMessage(msg); + builder.setPositiveButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mDPM.removeActiveAdmin(mDeviceAdmin.getComponent()); + finish(); + } + }); + builder.setNegativeButton(R.string.dlg_cancel, null); + return builder.create(); + } + default: + return super.onCreateDialog(id, args); + + } + } + + static void setViewVisibility(ArrayList<View> views, int visibility) { + final int N = views.size(); + for (int i=0; i<N; i++) { + views.get(i).setVisibility(visibility); + } + } + + void updateInterface() { + mAdminIcon.setImageDrawable(mDeviceAdmin.loadIcon(getPackageManager())); + mAdminName.setText(mDeviceAdmin.loadLabel(getPackageManager())); + try { + mAdminDescription.setText( + mDeviceAdmin.loadDescription(getPackageManager())); + mAdminDescription.setVisibility(View.VISIBLE); + } catch (Resources.NotFoundException e) { + mAdminDescription.setVisibility(View.GONE); + } + if (mAddMsgText != null) { + mAddMsg.setText(mAddMsgText); + mAddMsg.setVisibility(View.VISIBLE); + } else { + mAddMsg.setVisibility(View.GONE); + } + if (mDPM.isAdminActive(mDeviceAdmin.getComponent())) { + if (mActivePolicies.size() == 0) { + ArrayList<DeviceAdminInfo.PolicyInfo> policies = mDeviceAdmin.getUsedPolicies(); + for (int i=0; i<policies.size(); i++) { + DeviceAdminInfo.PolicyInfo pi = policies.get(i); + View view = AppSecurityPermissions.getPermissionItemView( + this, getText(pi.label), "", true); + mActivePolicies.add(view); + mAdminPolicies.addView(view); + } + } + setViewVisibility(mActivePolicies, View.VISIBLE); + setViewVisibility(mAddingPolicies, View.GONE); + mAdminWarning.setText(getString(R.string.device_admin_status, + mDeviceAdmin.getActivityInfo().applicationInfo.loadLabel(getPackageManager()))); + mTitle.setText(getText(R.string.active_device_admin_msg)); + mActionButton.setText(getText(R.string.remove_device_admin)); + mAdding = false; + } else { + if (mAddingPolicies.size() == 0) { + ArrayList<DeviceAdminInfo.PolicyInfo> policies = mDeviceAdmin.getUsedPolicies(); + for (int i=0; i<policies.size(); i++) { + DeviceAdminInfo.PolicyInfo pi = policies.get(i); + View view = AppSecurityPermissions.getPermissionItemView( + this, getText(pi.label), getText(pi.description), true); + mAddingPolicies.add(view); + mAdminPolicies.addView(view); + } + } + setViewVisibility(mAddingPolicies, View.VISIBLE); + setViewVisibility(mActivePolicies, View.GONE); + mAdminWarning.setText(getString(R.string.device_admin_warning, + mDeviceAdmin.getActivityInfo().applicationInfo.loadLabel(getPackageManager()))); + mTitle.setText(getText(R.string.add_device_admin_msg)); + mActionButton.setText(getText(R.string.add_device_admin)); + mAdding = true; + } + } + +} diff --git a/src/com/android/settings/DeviceAdminSettings.java b/src/com/android/settings/DeviceAdminSettings.java new file mode 100644 index 0000000..c3c74b6 --- /dev/null +++ b/src/com/android/settings/DeviceAdminSettings.java @@ -0,0 +1,189 @@ +/* + * 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 org.xmlpull.v1.XmlPullParserException; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ListActivity; +import android.app.admin.DeviceAdminInfo; +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteCallback; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class DeviceAdminSettings extends ListActivity { + static final String TAG = "DeviceAdminSettings"; + + static final int DIALOG_WARNING = 1; + + DevicePolicyManager mDPM; + final HashSet<ComponentName> mActiveAdmins = new HashSet<ComponentName>(); + final ArrayList<DeviceAdminInfo> mAvailableAdmins = new ArrayList<DeviceAdminInfo>(); + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE); + + setContentView(R.layout.device_admin_settings); + } + + @Override + protected void onResume() { + super.onResume(); + updateList(); + } + + void updateList() { + mActiveAdmins.clear(); + List<ComponentName> cur = mDPM.getActiveAdmins(); + if (cur != null) { + for (int i=0; i<cur.size(); i++) { + mActiveAdmins.add(cur.get(i)); + } + } + + mAvailableAdmins.clear(); + List<ResolveInfo> avail = getPackageManager().queryBroadcastReceivers( + new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED), + PackageManager.GET_META_DATA); + int count = avail == null ? 0 : avail.size(); + for (int i=0; i<count; i++) { + ResolveInfo ri = avail.get(i); + try { + DeviceAdminInfo dpi = new DeviceAdminInfo(this, ri); + if (dpi.isVisible() || mActiveAdmins.contains(dpi.getComponent())) { + mAvailableAdmins.add(dpi); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Skipping " + ri.activityInfo, e); + } catch (IOException e) { + Log.w(TAG, "Skipping " + ri.activityInfo, e); + } + } + + getListView().setAdapter(new PolicyListAdapter()); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + DeviceAdminInfo dpi = (DeviceAdminInfo)l.getAdapter().getItem(position); + Intent intent = new Intent(); + intent.setClass(this, DeviceAdminAdd.class); + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, dpi.getComponent()); + startActivity(intent); + } + + static class ViewHolder { + ImageView icon; + TextView name; + CheckBox checkbox; + TextView description; + } + + class PolicyListAdapter extends BaseAdapter { + final LayoutInflater mInflater; + + PolicyListAdapter() { + mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public boolean hasStableIds() { + return true; + } + + public int getCount() { + return mAvailableAdmins.size(); + } + + public Object getItem(int position) { + return mAvailableAdmins.get(position); + } + + public long getItemId(int position) { + return position; + } + + public boolean areAllItemsEnabled() { + return false; + } + + public boolean isEnabled(int position) { + return true; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v; + if (convertView == null) { + v = newView(parent); + } else { + v = convertView; + } + bindView(v, position); + return v; + } + + public View newView(ViewGroup parent) { + View v = mInflater.inflate(R.layout.device_admin_item, parent, false); + ViewHolder h = new ViewHolder(); + h.icon = (ImageView)v.findViewById(R.id.icon); + h.name = (TextView)v.findViewById(R.id.name); + h.checkbox = (CheckBox)v.findViewById(R.id.checkbox); + h.description = (TextView)v.findViewById(R.id.description); + v.setTag(h); + return v; + } + + public void bindView(View view, int position) { + ViewHolder vh = (ViewHolder) view.getTag(); + DeviceAdminInfo item = mAvailableAdmins.get(position); + vh.icon.setImageDrawable(item.loadIcon(getPackageManager())); + vh.name.setText(item.loadLabel(getPackageManager())); + vh.checkbox.setChecked(mActiveAdmins.contains(item.getComponent())); + try { + vh.description.setText(item.loadDescription(getPackageManager())); + } catch (Resources.NotFoundException e) { + } + } + } +} diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java index 82b7f28..a769c38 100644 --- a/src/com/android/settings/DeviceInfoSettings.java +++ b/src/com/android/settings/DeviceInfoSettings.java @@ -140,7 +140,7 @@ public class DeviceInfoSettings extends PreferenceActivity { "\\w+\\s+" + /* ignore: version */ "([^\\s]+)\\s+" + /* group 1: 2.6.22-omap1 */ "\\(([^\\s@]+(?:@[^\\s.]+)?)[^)]*\\)\\s+" + /* group 2: (xxxxxx@xxxxx.constant) */ - "\\(.*?(?:\\(.*?\\)).*?\\)\\s+" + /* ignore: (gcc ..) */ + "\\((?:[^(]*\\([^)]*\\))?[^)]*\\)\\s+" + /* ignore: (gcc ..) */ "([^\\s]+)\\s+" + /* group 3: #26 */ "(?:PREEMPT\\s+)?" + /* ignore: PREEMPT (optional) */ "(.+)"; /* group 4: date */ diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java new file mode 100644 index 0000000..fbb07c1 --- /dev/null +++ b/src/com/android/settings/DisplaySettings.java @@ -0,0 +1,205 @@ +/* + * 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.System.SCREEN_OFF_TIMEOUT; + +import java.util.ArrayList; + +import android.app.admin.DevicePolicyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.preference.CheckBoxPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.util.Log; +import android.view.IWindowManager; + +public class DisplaySettings extends PreferenceActivity implements + Preference.OnPreferenceChangeListener { + private static final String TAG = "DisplaySettings"; + + /** If there is no setting in the provider, use this. */ + private static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000; + + private static final String KEY_SCREEN_TIMEOUT = "screen_timeout"; + private static final String KEY_ANIMATIONS = "animations"; + private static final String KEY_ACCELEROMETER = "accelerometer"; + + private ListPreference mAnimations; + private CheckBoxPreference mAccelerometer; + private float[] mAnimationScales; + + private IWindowManager mWindowManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ContentResolver resolver = getContentResolver(); + mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + + addPreferencesFromResource(R.xml.display_settings); + + mAnimations = (ListPreference) findPreference(KEY_ANIMATIONS); + mAnimations.setOnPreferenceChangeListener(this); + mAccelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER); + mAccelerometer.setPersistent(false); + + ListPreference screenTimeoutPreference = + (ListPreference) findPreference(KEY_SCREEN_TIMEOUT); + screenTimeoutPreference.setValue(String.valueOf(Settings.System.getInt( + resolver, SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE))); + screenTimeoutPreference.setOnPreferenceChangeListener(this); + disableUnusableTimeouts(screenTimeoutPreference); + } + + private void disableUnusableTimeouts(ListPreference screenTimeoutPreference) { + final DevicePolicyManager dpm = + (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); + final long maxTimeout = dpm != null ? dpm.getMaximumTimeToLock(null) : 0; + if (maxTimeout == 0) { + return; // policy not enforced + } + final CharSequence[] entries = screenTimeoutPreference.getEntries(); + final CharSequence[] values = screenTimeoutPreference.getEntryValues(); + ArrayList<CharSequence> revisedEntries = new ArrayList<CharSequence>(); + ArrayList<CharSequence> revisedValues = new ArrayList<CharSequence>(); + for (int i = 0; i < values.length; i++) { + long timeout = Long.valueOf(values[i].toString()); + if (timeout <= maxTimeout) { + revisedEntries.add(entries[i]); + revisedValues.add(values[i]); + } + } + if (revisedEntries.size() != entries.length || revisedValues.size() != values.length) { + screenTimeoutPreference.setEntries( + revisedEntries.toArray(new CharSequence[revisedEntries.size()])); + screenTimeoutPreference.setEntryValues( + revisedValues.toArray(new CharSequence[revisedValues.size()])); + final int userPreference = Integer.valueOf(screenTimeoutPreference.getValue()); + if (userPreference <= maxTimeout) { + screenTimeoutPreference.setValue(String.valueOf(userPreference)); + } else { + // There will be no highlighted selection since nothing in the list matches + // maxTimeout. The user can still select anything less than maxTimeout. + // TODO: maybe append maxTimeout to the list and mark selected. + } + } + screenTimeoutPreference.setEnabled(revisedEntries.size() > 0); + } + + @Override + protected void onResume() { + super.onResume(); + + updateState(true); + } + + private void updateState(boolean force) { + int animations = 0; + try { + mAnimationScales = mWindowManager.getAnimationScales(); + } catch (RemoteException e) { + } + if (mAnimationScales != null) { + if (mAnimationScales.length >= 1) { + animations = ((int)(mAnimationScales[0]+.5f)) % 10; + } + if (mAnimationScales.length >= 2) { + animations += (((int)(mAnimationScales[1]+.5f)) & 0x7) * 10; + } + } + int idx = 0; + int best = 0; + CharSequence[] aents = mAnimations.getEntryValues(); + for (int i=0; i<aents.length; i++) { + int val = Integer.parseInt(aents[i].toString()); + if (val <= animations && val > best) { + best = val; + idx = i; + } + } + mAnimations.setValueIndex(idx); + updateAnimationsSummary(mAnimations.getValue()); + mAccelerometer.setChecked(Settings.System.getInt( + getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION, 0) != 0); + } + + private void updateAnimationsSummary(Object value) { + CharSequence[] summaries = getResources().getTextArray(R.array.animations_summaries); + CharSequence[] values = mAnimations.getEntryValues(); + for (int i=0; i<values.length; i++) { + //Log.i("foo", "Comparing entry "+ values[i] + " to current " + // + mAnimations.getValue()); + if (values[i].equals(value)) { + mAnimations.setSummary(summaries[i]); + break; + } + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mAccelerometer) { + Settings.System.putInt(getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION, + mAccelerometer.isChecked() ? 1 : 0); + } + return true; + } + + public boolean onPreferenceChange(Preference preference, Object objValue) { + final String key = preference.getKey(); + if (KEY_ANIMATIONS.equals(key)) { + try { + int value = Integer.parseInt((String) objValue); + if (mAnimationScales.length >= 1) { + mAnimationScales[0] = value%10; + } + if (mAnimationScales.length >= 2) { + mAnimationScales[1] = (value/10)%10; + } + try { + mWindowManager.setAnimationScales(mAnimationScales); + } catch (RemoteException e) { + } + updateAnimationsSummary(objValue); + } catch (NumberFormatException e) { + Log.e(TAG, "could not persist animation setting", e); + } + + } + if (KEY_SCREEN_TIMEOUT.equals(key)) { + int value = Integer.parseInt((String) objValue); + try { + Settings.System.putInt(getContentResolver(), + SCREEN_OFF_TIMEOUT, value); + } catch (NumberFormatException e) { + Log.e(TAG, "could not persist screen timeout setting", e); + } + } + + return true; + } +} diff --git a/src/com/android/settings/DockSettings.java b/src/com/android/settings/DockSettings.java index fe9aeb7..0d46ce9 100644 --- a/src/com/android/settings/DockSettings.java +++ b/src/com/android/settings/DockSettings.java @@ -18,15 +18,18 @@ package com.android.settings; import android.app.AlertDialog; import android.app.Dialog; +import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; +import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; +import android.provider.Settings; import com.android.settings.bluetooth.DockEventReceiver; @@ -34,7 +37,9 @@ public class DockSettings extends PreferenceActivity { private static final int DIALOG_NOT_DOCKED = 1; private static final String KEY_AUDIO_SETTINGS = "dock_audio"; + private static final String KEY_DOCK_SOUNDS = "dock_sounds"; private Preference mAudioSettings; + private CheckBoxPreference mDockSounds; private Intent mDockIntent; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -71,28 +76,47 @@ public class DockSettings extends PreferenceActivity { } private void initDockSettings() { + ContentResolver resolver = getContentResolver(); + mAudioSettings = findPreference(KEY_AUDIO_SETTINGS); if (mAudioSettings != null) { mAudioSettings.setSummary(R.string.dock_audio_summary_none); } + + mDockSounds = (CheckBoxPreference) findPreference(KEY_DOCK_SOUNDS); + mDockSounds.setPersistent(false); + mDockSounds.setChecked(Settings.System.getInt(resolver, + Settings.System.DOCK_SOUNDS_ENABLED, 0) != 0); } private void handleDockChange(Intent intent) { if (mAudioSettings != null) { int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, 0); - mDockIntent = intent; - int resId = R.string.dock_audio_summary_unknown; - switch (dockState) { - case Intent.EXTRA_DOCK_STATE_CAR: - resId = R.string.dock_audio_summary_car; - break; - case Intent.EXTRA_DOCK_STATE_DESK: - resId = R.string.dock_audio_summary_desk; - break; - case Intent.EXTRA_DOCK_STATE_UNDOCKED: - resId = R.string.dock_audio_summary_none; + + boolean isBluetooth = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) != null; + + if (!isBluetooth) { + // No dock audio if not on Bluetooth. + mAudioSettings.setEnabled(false); + mAudioSettings.setSummary(R.string.dock_audio_summary_unknown); + } else { + mAudioSettings.setEnabled(true); + + mDockIntent = intent; + int resId = R.string.dock_audio_summary_unknown; + switch (dockState) { + case Intent.EXTRA_DOCK_STATE_CAR: + resId = R.string.dock_audio_summary_car; + break; + case Intent.EXTRA_DOCK_STATE_DESK: + resId = R.string.dock_audio_summary_desk; + break; + case Intent.EXTRA_DOCK_STATE_UNDOCKED: + resId = R.string.dock_audio_summary_none; + } + mAudioSettings.setSummary(resId); } - mAudioSettings.setSummary(resId); + if (dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { // remove undocked dialog if currently showing. try { @@ -118,6 +142,9 @@ public class DockSettings extends PreferenceActivity { i.setClass(this, DockEventReceiver.class); sendBroadcast(i); } + } else if (preference == mDockSounds) { + Settings.System.putInt(getContentResolver(), Settings.System.DOCK_SOUNDS_ENABLED, + mDockSounds.isChecked() ? 1 : 0); } return true; diff --git a/src/com/android/settings/InstalledAppDetails.java b/src/com/android/settings/InstalledAppDetails.java index d05014b..0ca35b8 100644 --- a/src/com/android/settings/InstalledAppDetails.java +++ b/src/com/android/settings/InstalledAppDetails.java @@ -18,30 +18,36 @@ package com.android.settings; +import com.android.internal.content.PackageHelper; import com.android.settings.R; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageDataObserver; -import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageManager; +import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.PackageStats; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.storage.IMountService; import android.text.format.Formatter; -import android.util.Config; import android.util.Log; import java.util.ArrayList; import java.util.List; @@ -62,11 +68,13 @@ import android.widget.TextView; * For non-system applications, there is no option to clear data. Instead there is an option to * uninstall the application. */ -public class InstalledAppDetails extends Activity implements View.OnClickListener, DialogInterface.OnClickListener { +public class InstalledAppDetails extends Activity implements View.OnClickListener { private static final String TAG="InstalledAppDetails"; private static final int _UNKNOWN_APP=R.string.unknown; private ApplicationInfo mAppInfo; - private Button mAppButton; + private Button mUninstallButton; + private boolean mMoveInProgress = false; + private boolean mUpdatedSysApp = false; private Button mActivitiesButton; private boolean localLOGV = false; private TextView mAppVersion; @@ -76,15 +84,17 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene private PkgSizeObserver mSizeObserver; private ClearUserDataObserver mClearDataObserver; // Views related to cache info - private View mCachePanel; private TextView mCacheSize; private Button mClearCacheButton; private ClearCacheObserver mClearCacheObserver; private Button mForceStopButton; + private Button mClearDataButton; + private Button mMoveAppButton; + private int mMoveErrorCode; PackageStats mSizeInfo; - private Button mManageSpaceButton; private PackageManager mPm; + private PackageMoveObserver mPackageMoveObserver; //internal constants used in Handler private static final int OP_SUCCESSFUL = 1; @@ -92,6 +102,7 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene private static final int CLEAR_USER_DATA = 1; private static final int GET_PKG_SIZE = 2; private static final int CLEAR_CACHE = 3; + private static final int PACKAGE_MOVE = 4; private static final String ATTR_PACKAGE_STATS="PackageStats"; // invalid size value used initially and also when size retrieval through PackageManager @@ -101,7 +112,6 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene // Resource strings private CharSequence mInvalidSizeStr; private CharSequence mComputingStr; - private CharSequence mAppButtonText; // Dialog identifiers used in showDialog private static final int DLG_BASE = 0; @@ -109,18 +119,15 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene private static final int DLG_FACTORY_RESET = DLG_BASE + 2; private static final int DLG_APP_NOT_FOUND = DLG_BASE + 3; private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 4; - - // Possible btn states - private enum AppButtonStates { - CLEAR_DATA, - UNINSTALL, - FACTORY_RESET, - NONE - } - private AppButtonStates mAppButtonState; + private static final int DLG_FORCE_STOP = DLG_BASE + 5; + private static final int DLG_MOVE_FAILED = DLG_BASE + 6; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { + // If the activity is gone, don't process any more messages. + if (isFinishing()) { + return; + } switch (msg.what) { case CLEAR_USER_DATA: processClearMsg(msg); @@ -132,6 +139,9 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene // Refresh size info mPm.getPackageSizeInfo(mAppInfo.packageName, mSizeObserver); break; + case PACKAGE_MOVE: + processMoveMsg(msg); + break; default: break; } @@ -147,7 +157,6 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene } class PkgSizeObserver extends IPackageStatsObserver.Stub { - public int idx; public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) { Message msg = mHandler.obtainMessage(GET_PKG_SIZE); Bundle data = new Bundle(); @@ -161,10 +170,18 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene class ClearCacheObserver extends IPackageDataObserver.Stub { public void onRemoveCompleted(final String packageName, final boolean succeeded) { final Message msg = mHandler.obtainMessage(CLEAR_CACHE); - msg.arg1 = succeeded?OP_SUCCESSFUL:OP_FAILED; + msg.arg1 = succeeded ? OP_SUCCESSFUL:OP_FAILED; mHandler.sendMessage(msg); } } + + class PackageMoveObserver extends IPackageMoveObserver.Stub { + public void packageMoved(String packageName, int returnCode) throws RemoteException { + final Message msg = mHandler.obtainMessage(PACKAGE_MOVE); + msg.arg1 = returnCode; + mHandler.sendMessage(msg); + } + } private String getSizeStr(long size) { if (size == SIZE_INVALID) { @@ -173,31 +190,142 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene return Formatter.formatFileSize(this, size); } + private void initDataButtons() { + if (mAppInfo.manageSpaceActivityName != null) { + mClearDataButton.setText(R.string.manage_space_text); + } else { + mClearDataButton.setText(R.string.clear_user_data_text); + } + mClearDataButton.setOnClickListener(this); + } + + private CharSequence getMoveErrMsg(int errCode) { + switch (errCode) { + case PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE: + return getString(R.string.insufficient_storage); + case PackageManager.MOVE_FAILED_DOESNT_EXIST: + return getString(R.string.does_not_exist); + case PackageManager.MOVE_FAILED_FORWARD_LOCKED: + return getString(R.string.app_forward_locked); + case PackageManager.MOVE_FAILED_INVALID_LOCATION: + return getString(R.string.invalid_location); + case PackageManager.MOVE_FAILED_SYSTEM_PACKAGE: + return getString(R.string.system_package); + case PackageManager.MOVE_FAILED_INTERNAL_ERROR: + return ""; + } + return ""; + } + + private void initMoveButton() { + String pkgName = mAppInfo.packageName; + boolean dataOnly = false; + ApplicationInfo info1 = null; + PackageInfo pkgInfo = null; + + try { + info1 = mPm.getApplicationInfo(pkgName, 0); + pkgInfo = mPm.getPackageInfo(mAppInfo.packageName, + PackageManager.GET_UNINSTALLED_PACKAGES); + } catch (NameNotFoundException e) { + } + dataOnly = (info1 == null) && (mAppInfo != null); + boolean moveDisable = true; + if (dataOnly) { + mMoveAppButton.setText(R.string.move_app); + } else if ((mAppInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + mMoveAppButton.setText(R.string.move_app_to_internal); + // Always let apps move to internal storage from sdcard. + moveDisable = false; + } else { + mMoveAppButton.setText(R.string.move_app_to_sdcard); + if ((mAppInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0 && + (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 && + pkgInfo != null) { + if (pkgInfo.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL || + pkgInfo.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { + moveDisable = false; + } else if (pkgInfo.installLocation == PackageInfo.INSTALL_LOCATION_UNSPECIFIED) { + IPackageManager ipm = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + int loc; + try { + loc = ipm.getInstallLocation(); + } catch (RemoteException e) { + Log.e(TAG, "Is Pakage Manager running?"); + return; + } + if (loc == PackageHelper.APP_INSTALL_EXTERNAL) { + // For apps with no preference and the default value set + // to install on sdcard. + moveDisable = false; + } + } + } + } + if (moveDisable) { + mMoveAppButton.setEnabled(false); + } else { + mMoveAppButton.setOnClickListener(this); + mMoveAppButton.setEnabled(true); + } + } + + private void initUninstallButtons() { + mUpdatedSysApp = (mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + boolean enabled = true; + if (mUpdatedSysApp) { + mUninstallButton.setText(R.string.app_factory_reset); + } else { + if ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0){ + // Disable button for system applications. + enabled = false; + } + mUninstallButton.setText(R.string.uninstall_text); + } + mUninstallButton.setEnabled(enabled); + if (enabled) { + // Register listener + mUninstallButton.setOnClickListener(this); + } + } + + private boolean initAppInfo(String packageName) { + try { + mAppInfo = mPm.getApplicationInfo(packageName, + PackageManager.GET_UNINSTALLED_PACKAGES); + return true; + } catch (NameNotFoundException e) { + Log.e(TAG, "Exception when retrieving package: " + packageName, e); + showDialogInner(DLG_APP_NOT_FOUND); + return false; + } + } + /** Called when the activity is first created. */ @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); + // Get package manager mPm = getPackageManager(); + // Get application's name from intent Intent intent = getIntent(); final String packageName = intent.getStringExtra(ManageApplications.APP_PKG_NAME); - mComputingStr = getText(R.string.computing_size); + if (! initAppInfo(packageName)) { + return; // could not find package, finish called + } + // Try retrieving package stats again CharSequence totalSizeStr, appSizeStr, dataSizeStr; + mComputingStr = getText(R.string.computing_size); totalSizeStr = appSizeStr = dataSizeStr = mComputingStr; if(localLOGV) Log.i(TAG, "Have to compute package sizes"); mSizeObserver = new PkgSizeObserver(); - try { - mAppInfo = mPm.getApplicationInfo(packageName, - PackageManager.GET_UNINSTALLED_PACKAGES); - } catch (NameNotFoundException e) { - Log.e(TAG, "Exception when retrieving package:"+packageName, e); - showDialogInner(DLG_APP_NOT_FOUND); - return; - } setContentView(R.layout.installed_app_details); //TODO download str and download url + // Set default values on sizes mTotalSize = (TextView)findViewById(R.id.total_size_text); mTotalSize.setText(totalSizeStr); @@ -205,24 +333,28 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene mAppSize.setText(appSizeStr); mDataSize = (TextView)findViewById(R.id.data_size_text); mDataSize.setText(dataSizeStr); - // Get AppButton - mAppButton = ((Button)findViewById(R.id.uninstall_button)); - // Get ManageSpaceButton - mManageSpaceButton = (Button)findViewById(R.id.manage_space_button); - if(mAppInfo.manageSpaceActivityName != null) { - mManageSpaceButton.setVisibility(View.VISIBLE); - mManageSpaceButton.setOnClickListener(this); - } + + // Get Control button panel + View btnPanel = findViewById(R.id.control_buttons_panel); + mForceStopButton = (Button) btnPanel.findViewById(R.id.left_button); + mForceStopButton.setText(R.string.force_stop); + mUninstallButton = (Button)btnPanel.findViewById(R.id.right_button); + mForceStopButton.setEnabled(false); + + // Initialize clear data and move install location buttons + View data_buttons_panel = findViewById(R.id.data_buttons_panel); + mClearDataButton = (Button) data_buttons_panel.findViewById(R.id.left_button); + mMoveAppButton = (Button) data_buttons_panel.findViewById(R.id.right_button); + // Cache section - mCachePanel = findViewById(R.id.cache_panel); mCacheSize = (TextView) findViewById(R.id.cache_size_text); mCacheSize.setText(mComputingStr); mClearCacheButton = (Button) findViewById(R.id.clear_cache_button); - mForceStopButton = (Button) findViewById(R.id.force_stop_button); - mForceStopButton.setOnClickListener(this); + // Get list of preferred activities mActivitiesButton = (Button)findViewById(R.id.clear_activities_button); List<ComponentName> prefActList = new ArrayList<ComponentName>(); + // Intent list cannot be null. so pass empty list List<IntentFilter> intentList = new ArrayList<IntentFilter>(); mPm.getPreferredActivities(intentList, prefActList, packageName); @@ -250,95 +382,65 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene permsView.setVisibility(View.GONE); } } - - private void refreshAppAttributes(PackageInfo pkgInfo) { - setAppLabelAndIcon(); - // Version number of application - setAppVersion(pkgInfo); - setAppBtnState(); - // Refresh size info - if (mAppInfo != null && mAppInfo.packageName != null) { - mPm.getPackageSizeInfo(mAppInfo.packageName, mSizeObserver); - } - } - + // Utility method to set applicaiton label and icon. - private void setAppLabelAndIcon() { - ((ImageView)findViewById(R.id.app_icon)).setImageDrawable(mAppInfo.loadIcon(mPm)); - //set application name TODO version - CharSequence appName = mAppInfo.loadLabel(mPm); - if(appName == null) { - appName = getString(_UNKNOWN_APP); - } - ((TextView)findViewById(R.id.app_name)).setText(appName); - } - - // Utility method to set application version - private void setAppVersion(PackageInfo pkgInfo) { + private void setAppLabelAndIcon(PackageInfo pkgInfo) { + View appSnippet = findViewById(R.id.app_snippet); + ImageView icon = (ImageView) appSnippet.findViewById(R.id.app_icon); + icon.setImageDrawable(mAppInfo.loadIcon(mPm)); + // Set application name. + TextView label = (TextView) appSnippet.findViewById(R.id.app_name); + label.setText(mAppInfo.loadLabel(mPm)); // Version number of application - mAppVersion = ((TextView)findViewById(R.id.app_version)); - if (pkgInfo != null) { + mAppVersion = (TextView) appSnippet.findViewById(R.id.app_size); + + if (pkgInfo != null && pkgInfo.versionName != null) { mAppVersion.setVisibility(View.VISIBLE); mAppVersion.setText(getString(R.string.version_text, - String.valueOf(pkgInfo.versionCode))); + String.valueOf(pkgInfo.versionName))); } else { - mAppVersion.setVisibility(View.GONE); + mAppVersion.setVisibility(View.INVISIBLE); } } - // Utility method to set button state - private void setAppBtnState() { - boolean visible = true; - if ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - if ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { - mAppButtonState = AppButtonStates.FACTORY_RESET; - mAppButtonText = getText(R.string.app_factory_reset); - } else { - if ((mAppInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) == 0) { - // Hide button if diableClearUserData is set - mAppButtonState = AppButtonStates.NONE; - visible = false; - } else { - mAppButtonState = AppButtonStates.CLEAR_DATA; - mAppButtonText = getText(R.string.clear_user_data_text); - } - } - } else { - mAppButtonState = AppButtonStates.UNINSTALL; - mAppButtonText = getText(R.string.uninstall_text); + @Override + public void onResume() { + super.onResume(); + + if (mAppInfo == null) { + setIntentAndFinish(true, true); + return; // onCreate must have failed, make sure to exit } - if(visible) { - mAppButton.setText(mAppButtonText); - mAppButton.setVisibility(View.VISIBLE); - } else { - mAppButton.setVisibility(View.GONE); + if (! initAppInfo(mAppInfo.packageName)) { + return; // could not find package, finish called } - } - - @Override - public void onStart() { - super.onStart(); - PackageInfo pkgInfo; + + PackageInfo pkgInfo = null; // Get application info again to refresh changed properties of application try { - mAppInfo = mPm.getApplicationInfo(mAppInfo.packageName, - PackageManager.GET_UNINSTALLED_PACKAGES); pkgInfo = mPm.getPackageInfo(mAppInfo.packageName, PackageManager.GET_UNINSTALLED_PACKAGES); } catch (NameNotFoundException e) { Log.e(TAG, "Exception when retrieving package:" + mAppInfo.packageName, e); showDialogInner(DLG_APP_NOT_FOUND); - return; + return; // could not find package, finish called + } + + checkForceStop(); + setAppLabelAndIcon(pkgInfo); + refreshButtons(); + + // Refresh size info + if (mAppInfo != null && mAppInfo.packageName != null) { + mPm.getPackageSizeInfo(mAppInfo.packageName, mSizeObserver); } - refreshAppAttributes(pkgInfo); } - + private void setIntentAndFinish(boolean finish, boolean appChanged) { if(localLOGV) Log.i(TAG, "appChanged="+appChanged); Intent intent = new Intent(); intent.putExtra(ManageApplications.APP_CHG, appChanged); setResult(ManageApplications.RESULT_OK, intent); - mAppButton.setEnabled(false); if(finish) { finish(); } @@ -383,18 +485,11 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene mSizeInfo = newPs; } } - - long data = mSizeInfo.dataSize; - // Disable button if data is 0 - if(mAppButtonState != AppButtonStates.NONE){ - mAppButton.setText(mAppButtonText); - if((mAppButtonState == AppButtonStates.CLEAR_DATA) && (data == 0)) { - mAppButton.setEnabled(false); - } else { - mAppButton.setEnabled(true); - mAppButton.setOnClickListener(this); - } + // If data size is zero disable clear data button + if (newPs.dataSize == 0) { + mClearDataButton.setEnabled(false); } + long data = mSizeInfo.dataSize; refreshCacheInfo(newPs.cacheSize); } @@ -415,24 +510,57 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene private void processClearMsg(Message msg) { int result = msg.arg1; String packageName = mAppInfo.packageName; + mClearDataButton.setText(R.string.clear_user_data_text); if(result == OP_SUCCESSFUL) { - Log.i(TAG, "Cleared user data for system package:"+packageName); + Log.i(TAG, "Cleared user data for package : "+packageName); mPm.getPackageSizeInfo(packageName, mSizeObserver); } else { - mAppButton.setText(R.string.clear_user_data_text); - mAppButton.setEnabled(true); + mClearDataButton.setEnabled(true); } } - + + private void refreshButtons() { + if (!mMoveInProgress) { + initUninstallButtons(); + initDataButtons(); + initMoveButton(); + } else { + mMoveAppButton.setText(R.string.moving); + mMoveAppButton.setEnabled(false); + mUninstallButton.setEnabled(false); + } + } + + private void processMoveMsg(Message msg) { + int result = msg.arg1; + String packageName = mAppInfo.packageName; + // Refresh the button attributes. + mMoveInProgress = false; + if(result == PackageManager.MOVE_SUCCEEDED) { + Log.i(TAG, "Moved resources for " + packageName); + // Refresh size information again. + mPm.getPackageSizeInfo(packageName, mSizeObserver); + } else { + mMoveErrorCode = result; + showDialogInner(DLG_MOVE_FAILED); + } + + if (! initAppInfo(packageName)) { + return; // could not find package, finish called + } + + refreshButtons(); + } + /* * Private method to initiate clearing user data when the user clicks the clear data * button for a system package */ - private void initiateClearUserDataForSysPkg() { - mAppButton.setEnabled(false); - //invoke uninstall or clear user data based on sysPackage + private void initiateClearUserData() { + mClearDataButton.setEnabled(false); + // Invoke uninstall or clear user data based on sysPackage String packageName = mAppInfo.packageName; - Log.i(TAG, "Clearing user data for system package"); + Log.i(TAG, "Clearing user data for package : " + packageName); if(mClearDataObserver == null) { mClearDataObserver = new ClearUserDataObserver(); } @@ -443,7 +571,7 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene Log.i(TAG, "Couldnt clear application user data for package:"+packageName); showDialogInner(DLG_CANNOT_CLEAR_DATA); } else { - mAppButton.setText(R.string.recompute_size); + mClearDataButton.setText(R.string.recompute_size); } } @@ -453,23 +581,35 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene } @Override - public Dialog onCreateDialog(int id) { + public Dialog onCreateDialog(int id, Bundle args) { switch (id) { case DLG_CLEAR_DATA: return new AlertDialog.Builder(this) .setTitle(getString(R.string.clear_data_dlg_title)) .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(getString(R.string.clear_data_dlg_text)) - .setPositiveButton(R.string.dlg_ok, this) - .setNegativeButton(R.string.dlg_cancel, this) + .setPositiveButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Clear user data here + initiateClearUserData(); + } + }) + .setNegativeButton(R.string.dlg_cancel, null) .create(); case DLG_FACTORY_RESET: return new AlertDialog.Builder(this) .setTitle(getString(R.string.app_factory_reset_dlg_title)) .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(getString(R.string.app_factory_reset_dlg_text)) - .setPositiveButton(R.string.dlg_ok, this) - .setNegativeButton(R.string.dlg_cancel, this) + .setPositiveButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Clear user data here + uninstallPkg(mAppInfo.packageName); + } + }) + .setNegativeButton(R.string.dlg_cancel, null) .create(); case DLG_APP_NOT_FOUND: return new AlertDialog.Builder(this) @@ -492,11 +632,35 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { + mClearDataButton.setEnabled(false); //force to recompute changed value setIntentAndFinish(false, false); } }) .create(); + case DLG_FORCE_STOP: + return new AlertDialog.Builder(this) + .setTitle(getString(R.string.force_stop_dlg_title)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(getString(R.string.force_stop_dlg_text)) + .setPositiveButton(R.string.dlg_ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Force stop + forceStopPackage(mAppInfo.packageName); + } + }) + .setNegativeButton(R.string.dlg_cancel, null) + .create(); + case DLG_MOVE_FAILED: + CharSequence msg = getString(R.string.move_app_failed_dlg_text, + getMoveErrMsg(mMoveErrorCode)); + return new AlertDialog.Builder(this) + .setTitle(getString(R.string.move_app_failed_dlg_title)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(msg) + .setNeutralButton(R.string.dlg_ok, null) + .create(); } return null; } @@ -509,27 +673,53 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene setIntentAndFinish(true, true); } + private void forceStopPackage(String pkgName) { + ActivityManager am = (ActivityManager)getSystemService( + Context.ACTIVITY_SERVICE); + am.forceStopPackage(pkgName); + checkForceStop(); + } + + private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mForceStopButton.setEnabled(getResultCode() != RESULT_CANCELED); + mForceStopButton.setOnClickListener(InstalledAppDetails.this); + } + }; + + private void checkForceStop() { + Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, + Uri.fromParts("package", mAppInfo.packageName, null)); + intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppInfo.packageName }); + intent.putExtra(Intent.EXTRA_UID, mAppInfo.uid); + sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null, + Activity.RESULT_CANCELED, null, null); + } + /* * Method implementing functionality of buttons clicked * @see android.view.View.OnClickListener#onClick(android.view.View) */ public void onClick(View v) { String packageName = mAppInfo.packageName; - if(v == mAppButton) { - if (mAppButtonState == AppButtonStates.CLEAR_DATA) { - showDialogInner(DLG_CLEAR_DATA); - } else if (mAppButtonState == AppButtonStates.FACTORY_RESET) { + if(v == mUninstallButton) { + if (mUpdatedSysApp) { showDialogInner(DLG_FACTORY_RESET); - } else if (mAppButtonState == AppButtonStates.UNINSTALL) { + } else { uninstallPkg(packageName); } } else if(v == mActivitiesButton) { mPm.clearPackagePreferredActivities(packageName); mActivitiesButton.setEnabled(false); - } else if(v == mManageSpaceButton) { - Intent intent = new Intent(Intent.ACTION_DEFAULT); - intent.setClassName(mAppInfo.packageName, mAppInfo.manageSpaceActivityName); - startActivityForResult(intent, -1); + } else if(v == mClearDataButton) { + if (mAppInfo.manageSpaceActivityName != null) { + Intent intent = new Intent(Intent.ACTION_DEFAULT); + intent.setClassName(mAppInfo.packageName, mAppInfo.manageSpaceActivityName); + startActivityForResult(intent, -1); + } else { + showDialogInner(DLG_CLEAR_DATA); + } } else if (v == mClearCacheButton) { // Lazy initialization of observer if (mClearCacheObserver == null) { @@ -537,23 +727,16 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene } mPm.deleteApplicationCacheFiles(packageName, mClearCacheObserver); } else if (v == mForceStopButton) { - ActivityManager am = (ActivityManager)getSystemService( - Context.ACTIVITY_SERVICE); - am.restartPackage(packageName); - } - } - - public void onClick(DialogInterface dialog, int which) { - if(which == AlertDialog.BUTTON_POSITIVE) { - if (mAppButtonState == AppButtonStates.CLEAR_DATA) { - // Invoke uninstall or clear user data based on sysPackage - initiateClearUserDataForSysPkg(); - } else if (mAppButtonState == AppButtonStates.FACTORY_RESET) { - // Initiate package installer to delete package - uninstallPkg(mAppInfo.packageName); + forceStopPackage(mAppInfo.packageName); + } else if (v == mMoveAppButton) { + if (mPackageMoveObserver == null) { + mPackageMoveObserver = new PackageMoveObserver(); } - } else { - //cancel do nothing just retain existing screen + int moveFlags = (mAppInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0 ? + PackageManager.MOVE_INTERNAL : PackageManager.MOVE_EXTERNAL_MEDIA; + mMoveInProgress = true; + refreshButtons(); + mPm.movePackage(mAppInfo.packageName, mPackageMoveObserver, moveFlags); } } } diff --git a/src/com/android/settings/LanguageSettings.java b/src/com/android/settings/LanguageSettings.java index 1b9f0c1..91d260c 100644 --- a/src/com/android/settings/LanguageSettings.java +++ b/src/com/android/settings/LanguageSettings.java @@ -43,10 +43,12 @@ import java.util.List; public class LanguageSettings extends PreferenceActivity { + private static final String KEY_PHONE_LANGUAGE = "phone_language"; private boolean mHaveHardKeyboard; private List<InputMethodInfo> mInputMethodProperties; private List<CheckBoxPreference> mCheckboxes; + private Preference mLanguagePref; final TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':'); @@ -67,6 +69,8 @@ public class LanguageSettings extends PreferenceActivity { if (getAssets().getLocales().length == 1) { getPreferenceScreen(). removePreference(findPreference("language_category")); + } else { + mLanguagePref = findPreference(KEY_PHONE_LANGUAGE); } Configuration config = getResources().getConfiguration(); @@ -116,7 +120,11 @@ public class LanguageSettings extends PreferenceActivity { // If setting activity is available, add a setting screen entry. if (null != property.getSettingsActivity()) { PreferenceScreen prefScreen = new PreferenceScreen(this, null); - prefScreen.setKey(property.getSettingsActivity()); + String settingsActivity = property.getSettingsActivity(); + if (settingsActivity.lastIndexOf("/") < 0) { + settingsActivity = property.getPackageName() + "/" + settingsActivity; + } + prefScreen.setKey(settingsActivity); prefScreen.setTitle(label); if (N == 1) { prefScreen.setSummary(getString(R.string.onscreen_keyboard_settings_summary)); @@ -156,6 +164,15 @@ public class LanguageSettings extends PreferenceActivity { } } mLastTickedInputMethodId = null; + + if (mLanguagePref != null) { + Configuration conf = getResources().getConfiguration(); + String locale = conf.locale.getDisplayName(conf.locale); + if (locale != null && locale.length() > 1) { + locale = Character.toUpperCase(locale.charAt(0)) + locale.substring(1); + mLanguagePref.setSummary(locale); + } + } } @Override @@ -163,7 +180,8 @@ public class LanguageSettings extends PreferenceActivity { super.onPause(); StringBuilder builder = new StringBuilder(256); - + StringBuilder disabledSysImes = new StringBuilder(256); + int firstEnabled = -1; int N = mInputMethodProperties.size(); for (int i = 0; i < N; ++i) { @@ -182,6 +200,12 @@ public class LanguageSettings extends PreferenceActivity { } else if (hasIt) { mLastInputMethodId = mLastTickedInputMethodId; } + // If it's a disabled system ime, add it to the disabled list so that it + // doesn't get enabled automatically on any changes to the package list + if (pref != null && !pref.isChecked() && systemIme && mHaveHardKeyboard) { + if (disabledSysImes.length() > 0) disabledSysImes.append(":"); + disabledSysImes.append(id); + } } // If the last input method is unset, set it as the first enabled one. @@ -196,6 +220,8 @@ public class LanguageSettings extends PreferenceActivity { Settings.Secure.putString(getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); Settings.Secure.putString(getContentResolver(), + Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, disabledSysImes.toString()); + Settings.Secure.putString(getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, mLastInputMethodId != null ? mLastInputMethodId : ""); } @@ -261,6 +287,11 @@ public class LanguageSettings extends PreferenceActivity { String activityName = pref.getKey(); String packageName = activityName.substring(0, activityName .lastIndexOf(".")); + int slash = activityName.indexOf("/"); + if (slash > 0) { + packageName = activityName.substring(0, slash); + activityName = activityName.substring(slash + 1); + } if (activityName.length() > 0) { Intent i = new Intent(Intent.ACTION_MAIN); i.setClassName(packageName, activityName); diff --git a/src/com/android/settings/LauncherAppWidgetBinder.java b/src/com/android/settings/LauncherAppWidgetBinder.java deleted file mode 100644 index 98ea246..0000000 --- a/src/com/android/settings/LauncherAppWidgetBinder.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2008 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.app.Activity; -import android.appwidget.AppWidgetManager; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Intent; -import android.content.ComponentName; -import android.database.Cursor; -import android.database.SQLException; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Bundle; -import android.provider.BaseColumns; -import android.util.Log; - -import java.util.ArrayList; - -public class LauncherAppWidgetBinder extends Activity { - private static final String TAG = "LauncherAppWidgetBinder"; - private static final boolean LOGD = true; - - static final String AUTHORITY = "com.android.launcher.settings"; - static final String TABLE_FAVORITES = "favorites"; - - static final String EXTRA_BIND_SOURCES = "com.android.launcher.settings.bindsources"; - static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets"; - - static final String EXTRA_APPWIDGET_BITMAPS = "com.android.camera.appwidgetbitmaps"; - - /** - * {@link ContentProvider} constants pulled over from Launcher - */ - static final class LauncherProvider implements BaseColumns { - static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + TABLE_FAVORITES); - - static final String ITEM_TYPE = "itemType"; - static final String APPWIDGET_ID = "appWidgetId"; - static final String ICON = "icon"; - - static final int ITEM_TYPE_APPWIDGET = 4; - static final int ITEM_TYPE_WIDGET_CLOCK = 1000; - static final int ITEM_TYPE_WIDGET_SEARCH = 1001; - static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002; - } - - static final String[] BIND_PROJECTION = new String[] { - LauncherProvider._ID, - LauncherProvider.ITEM_TYPE, - LauncherProvider.APPWIDGET_ID, - LauncherProvider.ICON, - }; - - static final int INDEX_ID = 0; - static final int INDEX_ITEM_TYPE = 1; - static final int INDEX_APPWIDGET_ID = 2; - static final int INDEX_ICON = 3; - - static final ComponentName BIND_PHOTO_APPWIDGET = new ComponentName("com.android.camera", - "com.android.camera.PhotoAppWidgetBind"); - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - finish(); - - // This helper reaches into the Launcher database and binds any unlinked - // widgets. If will remove any items that can't be bound successfully. - // We protect this binder at the manifest level by asserting the caller - // has the Launcher WRITE_SETTINGS permission. - - final Intent intent = getIntent(); - final Bundle extras = intent.getExtras(); - - int[] bindSources = null; - ArrayList<ComponentName> bindTargets = null; - Exception exception = null; - - try { - bindSources = extras.getIntArray(EXTRA_BIND_SOURCES); - bindTargets = intent.getParcelableArrayListExtra(EXTRA_BIND_TARGETS); - } catch (ClassCastException ex) { - exception = ex; - } - - if (exception != null || bindSources == null || bindTargets == null || - bindSources.length != bindTargets.size()) { - Log.w(TAG, "Problem reading incoming bind request, or invalid request", exception); - return; - } - - final String selectWhere = buildOrWhereString(LauncherProvider.ITEM_TYPE, bindSources); - - final ContentResolver resolver = getContentResolver(); - final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); - - boolean foundPhotoAppWidgets = false; - final ArrayList<Integer> photoAppWidgetIds = new ArrayList<Integer>(); - final ArrayList<Bitmap> photoBitmaps = new ArrayList<Bitmap>(); - - Cursor c = null; - - try { - c = resolver.query(LauncherProvider.CONTENT_URI, - BIND_PROJECTION, selectWhere, null, null); - - if (LOGD) Log.d(TAG, "found bind cursor count="+c.getCount()); - - final ContentValues values = new ContentValues(); - while (c != null && c.moveToNext()) { - long favoriteId = c.getLong(INDEX_ID); - int itemType = c.getInt(INDEX_ITEM_TYPE); - int appWidgetId = c.getInt(INDEX_APPWIDGET_ID); - byte[] iconData = c.getBlob(INDEX_ICON); - - // Find the binding target for this type - ComponentName targetAppWidget = null; - for (int i = 0; i < bindSources.length; i++) { - if (bindSources[i] == itemType) { - targetAppWidget = bindTargets.get(i); - break; - } - } - - if (LOGD) Log.d(TAG, "found matching targetAppWidget="+targetAppWidget.toString()+" for favoriteId="+favoriteId); - - boolean bindSuccess = false; - try { - appWidgetManager.bindAppWidgetId(appWidgetId, targetAppWidget); - bindSuccess = true; - } catch (RuntimeException ex) { - Log.w(TAG, "Problem binding widget", ex); - } - - // Handle special case of photo widget by loading bitmap and - // preparing for later binding - if (bindSuccess && iconData != null && - itemType == LauncherProvider.ITEM_TYPE_WIDGET_PHOTO_FRAME) { - Bitmap bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.length); - - photoAppWidgetIds.add(appWidgetId); - photoBitmaps.add(bitmap); - foundPhotoAppWidgets = true; - } - - if (LOGD) Log.d(TAG, "after finished, success="+bindSuccess); - - // Depending on success, update launcher or remove item - Uri favoritesUri = ContentUris.withAppendedId(LauncherProvider.CONTENT_URI, favoriteId); - if (bindSuccess) { - values.clear(); - values.put(LauncherProvider.ITEM_TYPE, LauncherProvider.ITEM_TYPE_APPWIDGET); - values.putNull(LauncherProvider.ICON); - resolver.update(favoritesUri, values, null, null); - } else { - resolver.delete(favoritesUri, null, null); - } - - } - } catch (SQLException ex) { - Log.w(TAG, "Problem while binding appWidgetIds for Launcher", ex); - } finally { - if (c != null) { - c.close(); - } - } - - if (foundPhotoAppWidgets) { - // Convert appWidgetIds into int[] - final int N = photoAppWidgetIds.size(); - final int[] photoAppWidgetIdsArray = new int[N]; - for (int i = 0; i < N; i++) { - photoAppWidgetIdsArray[i] = photoAppWidgetIds.get(i); - } - - // Launch intent over to handle bitmap binding, but we don't need to - // wait around for the result. - final Intent bindIntent = new Intent(); - bindIntent.setComponent(BIND_PHOTO_APPWIDGET); - - final Bundle bindExtras = new Bundle(); - bindExtras.putIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS, photoAppWidgetIdsArray); - bindExtras.putParcelableArrayList(EXTRA_APPWIDGET_BITMAPS, photoBitmaps); - bindIntent.putExtras(bindExtras); - - startActivity(bindIntent); - } - - if (LOGD) Log.d(TAG, "completely finished with binding for Launcher"); - } - - /** - * Build a query string that will match any row where the column matches - * anything in the values list. - */ - static String buildOrWhereString(String column, int[] values) { - StringBuilder selectWhere = new StringBuilder(); - for (int i = values.length - 1; i >= 0; i--) { - selectWhere.append(column).append("=").append(values[i]); - if (i > 0) { - selectWhere.append(" OR "); - } - } - return selectWhere.toString(); - } - -} diff --git a/src/com/android/settings/LocalePicker.java b/src/com/android/settings/LocalePicker.java index d31e82c..dcd6141 100644 --- a/src/com/android/settings/LocalePicker.java +++ b/src/com/android/settings/LocalePicker.java @@ -19,7 +19,7 @@ package com.android.settings; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.ListActivity; -import android.backup.BackupManager; +import android.app.backup.BackupManager; import android.content.res.Configuration; import android.os.Bundle; import android.os.RemoteException; @@ -37,6 +37,7 @@ import java.util.Locale; public class LocalePicker extends ListActivity { private static final String TAG = "LocalePicker"; + private static final boolean DEBUG = false; Loc[] mLocales; String[] mSpecialLocaleCodes; @@ -90,8 +91,9 @@ public class LocalePicker extends ListActivity { Locale l = new Locale(language, country); if (finalSize == 0) { - Log.v(TAG, "adding initial "+ - toTitleCase(l.getDisplayLanguage(l))); + if (DEBUG) { + Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l))); + } preprocess[finalSize++] = new Loc(toTitleCase(l.getDisplayLanguage(l)), l); } else { @@ -101,13 +103,16 @@ public class LocalePicker extends ListActivity { // diff lang -> insert ours with lang-only name if (preprocess[finalSize-1].locale.getLanguage().equals( language)) { - Log.v(TAG, "backing up and fixing "+ - preprocess[finalSize-1].label+" to "+ - getDisplayName(preprocess[finalSize-1].locale)); + if (DEBUG) { + Log.v(TAG, "backing up and fixing "+ + preprocess[finalSize-1].label+" to "+ + getDisplayName(preprocess[finalSize-1].locale)); + } preprocess[finalSize-1].label = toTitleCase( getDisplayName(preprocess[finalSize-1].locale)); - Log.v(TAG, " and adding "+ - toTitleCase(getDisplayName(l))); + if (DEBUG) { + Log.v(TAG, " and adding "+ toTitleCase(getDisplayName(l))); + } preprocess[finalSize++] = new Loc(toTitleCase(getDisplayName(l)), l); } else { @@ -117,7 +122,9 @@ public class LocalePicker extends ListActivity { } else { displayName = toTitleCase(l.getDisplayLanguage(l)); } - Log.v(TAG, "adding "+displayName); + if (DEBUG) { + Log.v(TAG, "adding "+displayName); + } preprocess[finalSize++] = new Loc(displayName, l); } } diff --git a/src/com/android/settings/ManageApplications.java b/src/com/android/settings/ManageApplications.java index 9cd6e23..dfff8c9 100644 --- a/src/com/android/settings/ManageApplications.java +++ b/src/com/android/settings/ManageApplications.java @@ -17,11 +17,11 @@ package com.android.settings; import com.android.settings.R; + import android.app.ActivityManager; -import android.app.AlertDialog; import android.app.Dialog; -import android.app.ListActivity; import android.app.ProgressDialog; +import android.app.TabActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -42,7 +42,6 @@ import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.text.format.Formatter; -import android.util.Config; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -56,6 +55,7 @@ import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; import android.widget.ListView; +import android.widget.TabHost; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; @@ -81,42 +81,46 @@ import java.util.concurrent.CountDownLatch; * options to uninstall/delete user data for system applications. This activity * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE * intent. - * Initially a compute in progress message is displayed while the application retrieves - * the list of application information from the PackageManager. The size information - * for each package is refreshed to the screen. The resource(app description and - * icon) information for each package is not available yet, so some default values for size - * icon and descriptions are used initially. Later the resource information for each - * application is retrieved and dynamically updated on the screen. - * A Broadcast receiver registers for package additions or deletions when the activity is - * in focus. If the user installs or deletes packages when the activity has focus, the receiver - * gets notified and proceeds to add/delete these packages from the list on the screen. - * This is an unlikely scenario but could happen. The entire list gets created every time - * the activity's onStart gets invoked. This is to avoid having the receiver for the entire - * life cycle of the application. - * The applications can be sorted either alphabetically or - * based on size(descending). If this activity gets launched under low memory - * situations(A low memory notification dispatches intent - * ACTION_MANAGE_PACKAGE_STORAGE) the list is sorted per size. - * If the user selects an application, extended info(like size, uninstall/clear data options, - * permissions info etc.,) is displayed via the InstalledAppDetails activity. + * + * Initially a compute in progress message is displayed while the application retrieves + * the list of application information from the PackageManager. The size information + * for each package is refreshed to the screen. The resource (app description and + * icon) information for each package is not available yet, so some default values for size + * icon and descriptions are used initially. Later the resource information for each + * application is retrieved and dynamically updated on the screen. + * + * A Broadcast receiver registers for package additions or deletions when the activity is + * in focus. If the user installs or deletes packages when the activity has focus, the receiver + * gets notified and proceeds to add/delete these packages from the list on the screen. + * This is an unlikely scenario but could happen. The entire list gets created every time + * the activity's onStart gets invoked. This is to avoid having the receiver for the entire + * life cycle of the application. + * + * The applications can be sorted either alphabetically or + * based on size (descending). If this activity gets launched under low memory + * situations (a low memory notification dispatches intent + * ACTION_MANAGE_PACKAGE_STORAGE) the list is sorted per size. + * + * If the user selects an application, extended info (like size, uninstall/clear data options, + * permissions info etc.,) is displayed via the InstalledAppDetails activity. */ -public class ManageApplications extends ListActivity implements +public class ManageApplications extends TabActivity implements OnItemClickListener, DialogInterface.OnCancelListener, - DialogInterface.OnClickListener { + TabHost.TabContentFactory, + TabHost.OnTabChangeListener { // TAG for this activity private static final String TAG = "ManageApplications"; private static final String PREFS_NAME = "ManageAppsInfo.prefs"; private static final String PREF_DISABLE_CACHE = "disableCache"; // Log information boolean - private boolean localLOGV = Config.LOGV || false; + private boolean localLOGV = false; private static final boolean DEBUG_SIZE = false; private static final boolean DEBUG_TIME = false; // attributes used as keys when passing values to InstalledAppDetails activity - public static final String APP_PKG_PREFIX = "com.android.settings."; - public static final String APP_PKG_NAME = APP_PKG_PREFIX+"ApplicationPkgName"; - public static final String APP_CHG = APP_PKG_PREFIX+"changed"; + public static final String APP_PKG_NAME = "pkg"; + public static final String APP_CHG = "chg"; // attribute name used in receiver for tagging names of added/deleted packages private static final String ATTR_PKG_NAME="p"; @@ -138,11 +142,10 @@ public class ManageApplications extends ListActivity implements public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 0; public static final int FILTER_APPS_RUNNING = MENU_OPTIONS_BASE + 1; public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 2; - public static final int FILTER_OPTIONS = MENU_OPTIONS_BASE + 3; + public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 3; + public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 4; public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5; - // Alert Dialog presented to user to find out the filter option - private AlertDialog mAlertDlg; // sort order private int mSortOrder = SORT_ORDER_ALPHA; // Filter value @@ -174,7 +177,7 @@ public class ManageApplications extends ListActivity implements private PackageIntentReceiver mReceiver; // atomic variable used to track if computing pkg sizes is in progress. should be volatile? - private boolean mComputeSizes = false; + private boolean mComputeSizesFinished = false; // default icon thats used when displaying applications initially before resource info is // retrieved private static Drawable mDefaultAppIcon; @@ -207,13 +210,13 @@ public class ManageApplications extends ListActivity implements ResourceLoaderThread mResourceThread; private TaskRunner mSizeComputor; - String mCurrentPkgName; + private String mCurrentPkgName; // Cache application attributes private AppInfoCache mCache = new AppInfoCache(); // Boolean variables indicating state - private boolean mLoadLabels = false; + private boolean mLoadLabelsFinished = false; private boolean mSizesFirst = false; // ListView used to display list private ListView mListView; @@ -225,33 +228,38 @@ public class ManageApplications extends ListActivity implements private boolean mSetListViewLater = true; /* - * Handler class to handle messages for various operations + * Handler class to handle messages for various operations. * Most of the operations that effect Application related data * are posted as messages to the handler to avoid synchronization * when accessing these structures. + * * When the size retrieval gets kicked off for the first time, a COMPUTE_PKG_SIZE_START - * message is posted to the handler which invokes the getSizeInfo for the pkg at index 0 + * message is posted to the handler which invokes the getSizeInfo for the pkg at index 0. + * * When the PackageManager's asynchronous call back through * PkgSizeObserver.onGetStatsCompleted gets invoked, the application resources like - * label, description, icon etc., is loaded in the same thread and these values are - * set on the observer. The observer then posts a COMPUTE_PKG_SIZE_DONE message - * to the handler. This information is updated on the AppInfoAdapter associated with + * label, description, icon etc., are loaded in the same thread and these values are + * set on the observer. The observer then posts a COMPUTE_PKG_SIZE_DONE message + * to the handler. This information is updated on the AppInfoAdapter associated with * the list view of this activity and size info retrieval is initiated for the next package as - * indicated by mComputeIndex + * indicated by mComputeIndex. + * * When a package gets added while the activity has focus, the PkgSizeObserver posts * ADD_PKG_START message to the handler. If the computation is not in progress, the size * is retrieved for the newly added package through the observer object and the newly - * installed app info is updated on the screen. If the computation is still in progress + * installed app info is updated on the screen. If the computation is still in progress * the package is added to an internal structure and action deferred till the computation - * is done for all the packages. + * is done for all the packages. + * * When a package gets deleted, REMOVE_PKG is posted to the handler - * if computation is not in progress(as indicated by - * mDoneIniting), the package is deleted from the displayed list of apps. If computation is + * if computation is not in progress (as indicated by + * mDoneIniting), the package is deleted from the displayed list of apps. If computation is * still in progress the package is added to an internal structure and action deferred till * the computation is done for all packages. + * * When the sizes of all packages is computed, the newly * added or removed packages are processed in order. - * If the user changes the order in which these applications are viewed by hitting the + * If the user changes the order in which these applications are viewed by hitting the * menu key, REORDER_LIST message is posted to the handler. this sorts the list * of items based on the sort order. */ @@ -293,8 +301,8 @@ public class ManageApplications extends ListActivity implements } mAppInfoAdapter.bulkUpdateSizes(pkgs, sizes, formatted); break; - case COMPUTE_END : - mComputeSizes = true; + case COMPUTE_END: + mComputeSizesFinished = true; mFirst = true; mHandler.sendEmptyMessage(NEXT_LOAD_STEP); break; @@ -304,7 +312,7 @@ public class ManageApplications extends ListActivity implements Log.w(TAG, "Ignoring message:REMOVE_PKG for null pkgName"); break; } - if (!mComputeSizes) { + if (!mComputeSizesFinished) { Boolean currB = mAddRemoveMap.get(pkgName); if (currB == null || (currB.equals(Boolean.TRUE))) { mAddRemoveMap.put(pkgName, Boolean.FALSE); @@ -344,7 +352,7 @@ public class ManageApplications extends ListActivity implements Log.w(TAG, "Ignoring message:ADD_PKG_START for null pkgName"); break; } - if (!mComputeSizes || !mLoadLabels) { + if (!mComputeSizesFinished || !mLoadLabelsFinished) { Boolean currB = mAddRemoveMap.get(pkgName); if (currB == null || (currB.equals(Boolean.FALSE))) { mAddRemoveMap.put(pkgName, Boolean.TRUE); @@ -389,7 +397,7 @@ public class ManageApplications extends ListActivity implements } break; case REFRESH_DONE: - mLoadLabels = true; + mLoadLabelsFinished = true; mHandler.sendEmptyMessage(NEXT_LOAD_STEP); break; case NEXT_LOAD_STEP: @@ -399,7 +407,7 @@ public class ManageApplications extends ListActivity implements mSetListViewLater = false; mFirst = true; } - if (mComputeSizes && mLoadLabels) { + if (mComputeSizesFinished && mLoadLabelsFinished) { doneLoadingData(); // Check for added/removed packages Set<String> keys = mAddRemoveMap.keySet(); @@ -413,7 +421,7 @@ public class ManageApplications extends ListActivity implements } } mAddRemoveMap.clear(); - } else if (!mComputeSizes && !mLoadLabels) { + } else if (!mComputeSizesFinished && !mLoadLabelsFinished) { // Either load the package labels or initiate get size info if (mSizesFirst) { initComputeSizes(); @@ -426,9 +434,9 @@ public class ManageApplications extends ListActivity implements initListView(); mSetListViewLater = false; } - if (!mComputeSizes) { + if (!mComputeSizesFinished) { initComputeSizes(); - } else if (!mLoadLabels) { + } else if (!mLoadLabelsFinished) { initResourceThread(); } } @@ -612,7 +620,16 @@ public class ManageApplications extends ListActivity implements if (installedAppList == null) { return new ArrayList<ApplicationInfo> (); } - if (filterOption == FILTER_APPS_THIRD_PARTY) { + if (filterOption == FILTER_APPS_SDCARD) { + List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> (); + for (ApplicationInfo appInfo : installedAppList) { + if ((appInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + // App on sdcard + appList.add(appInfo); + } + } + return appList; + } else if (filterOption == FILTER_APPS_THIRD_PARTY) { List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> (); for (ApplicationInfo appInfo : installedAppList) { boolean flag = false; @@ -680,7 +697,21 @@ public class ManageApplications extends ListActivity implements if(pAppList == null) { return retList; } - if (filterOption == FILTER_APPS_THIRD_PARTY) { + if (filterOption == FILTER_APPS_SDCARD) { + for (ApplicationInfo appInfo : pAppList) { + boolean flag = false; + if ((appInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + // App on sdcard + flag = true; + } + if (flag) { + if (matchFilter(filter, filterMap, appInfo.packageName)) { + retList.add(appInfo); + } + } + } + return retList; + } else if (filterOption == FILTER_APPS_THIRD_PARTY) { for (ApplicationInfo appInfo : pAppList) { boolean flag = false; if ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { @@ -740,8 +771,8 @@ public class ManageApplications extends ListActivity implements // Some initialization code used when kicking off the size computation private void initAppList(List<ApplicationInfo> appList, int filterOption) { setProgressBarIndeterminateVisibility(true); - mComputeSizes = false; - mLoadLabels = false; + mComputeSizesFinished = false; + mLoadLabelsFinished = false; // Initialize lists mAddRemoveMap = new TreeMap<String, Boolean>(); mAppInfoAdapter.initMapFromList(appList, filterOption); @@ -769,7 +800,7 @@ public class ManageApplications extends ListActivity implements if ((appList != null) && (appList.size()) > 0) { mSizeComputor = new TaskRunner(appList); } else { - mComputeSizes = true; + mComputeSizesFinished = true; } } @@ -862,8 +893,8 @@ public class ManageApplications extends ListActivity implements static private class AppInfo { public String pkgName; int index; - public CharSequence appName; - public Drawable appIcon; + public CharSequence appName; + public Drawable appIcon; public CharSequence appSize; long size; @@ -1085,7 +1116,7 @@ public class ManageApplications extends ListActivity implements Log.w(TAG, "Invalid view position:"+position+", actual size is:"+mAppLocalList.size()); return null; } - // A ViewHolder keeps references to children views to avoid unneccessary calls + // A ViewHolder keeps references to children views to avoid unnecessary calls // to findViewById() on each row. AppViewHolder holder; @@ -1243,6 +1274,10 @@ public class ManageApplications extends ListActivity implements } else if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { return true; } + } else if (filterOption == FILTER_APPS_SDCARD) { + if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return true; + } } else { return true; } @@ -1529,20 +1564,42 @@ public class ManageApplications extends ListActivity implements filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); ManageApplications.this.registerReceiver(this, filter); + // Register for events related to sdcard installation. + IntentFilter sdFilter = new IntentFilter(); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + ManageApplications.this.registerReceiver(this, sdFilter); + } + @Override + public void onReceive(Context context, Intent intent) { + String actionStr = intent.getAction(); + if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr) || + Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) { + Uri data = intent.getData(); + String pkgName = data.getEncodedSchemeSpecificPart(); + updatePackageList(actionStr, pkgName); + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) || + Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) { + // When applications become available or unavailable (perhaps because + // the SD card was inserted or ejected) we need to refresh the + // AppInfo with new label, icon and size information as appropriate + // given the newfound (un)availability of the application. + // A simple way to do that is to treat the refresh as a package + // removal followed by a package addition. + String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + if (pkgList == null || pkgList.length == 0) { + // Ignore + return; + } + for (String pkgName : pkgList) { + updatePackageList(Intent.ACTION_PACKAGE_REMOVED, pkgName); + updatePackageList(Intent.ACTION_PACKAGE_ADDED, pkgName); + } + } } - @Override - public void onReceive(Context context, Intent intent) { - String actionStr = intent.getAction(); - Uri data = intent.getData(); - String pkgName = data.getEncodedSchemeSpecificPart(); - if (localLOGV) Log.i(TAG, "action:"+actionStr+", for package:"+pkgName); - updatePackageList(actionStr, pkgName); - } } - + private void updatePackageList(String actionStr, String pkgName) { - // technically we dont have to invoke handler since onReceive is invoked on - // the main thread but doing it here for better clarity if (Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) { Bundle data = new Bundle(); data.putString(ATTR_PKG_NAME, pkgName); @@ -1554,6 +1611,11 @@ public class ManageApplications extends ListActivity implements } } + static final String TAB_DOWNLOADED = "Downloaded"; + static final String TAB_RUNNING = "Running"; + static final String TAB_ALL = "All"; + static final String TAB_SDCARD = "OnSdCard"; + private View mRootView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -1564,9 +1626,11 @@ public class ManageApplications extends ListActivity implements } Intent intent = getIntent(); String action = intent.getAction(); + String defaultTabTag = TAB_DOWNLOADED; if (action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) { mSortOrder = SORT_ORDER_SIZE; mFilterApps = FILTER_APPS_ALL; + defaultTabTag = TAB_ALL; mSizesFirst = true; } mPm = getPackageManager(); @@ -1574,20 +1638,20 @@ public class ManageApplications extends ListActivity implements requestWindowFeature(Window.FEATURE_RIGHT_ICON); requestWindowFeature(Window.FEATURE_PROGRESS); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - setContentView(R.layout.compute_sizes); showLoadingMsg(); - mDefaultAppIcon =Resources.getSystem().getDrawable( + mDefaultAppIcon = Resources.getSystem().getDrawable( com.android.internal.R.drawable.sym_def_app_icon); mInvalidSizeStr = getText(R.string.invalid_size_value); mComputingSizeStr = getText(R.string.computing_size); // initialize the inflater mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mRootView = mInflater.inflate(R.layout.compute_sizes, null); mReceiver = new PackageIntentReceiver(); mObserver = new PkgSizeObserver(); // Create adapter and list view here - List<ApplicationInfo> appList = getInstalledApps(mSortOrder); + List<ApplicationInfo> appList = getInstalledApps(FILTER_APPS_ALL); mAppInfoAdapter = new AppInfoAdapter(this, appList); - ListView lv= (ListView) findViewById(android.R.id.list); + ListView lv = (ListView) mRootView.findViewById(android.R.id.list); lv.setOnItemClickListener(this); lv.setSaveEnabled(true); lv.setItemsCanFocus(true); @@ -1607,8 +1671,29 @@ public class ManageApplications extends ListActivity implements if (DEBUG_TIME) { Log.i(TAG, "Took " + (SystemClock.elapsedRealtime()-sStart) + " ms to init cache"); } + + final TabHost tabHost = getTabHost(); + tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED) + .setIndicator(getString(R.string.filter_apps_third_party), + getResources().getDrawable(R.drawable.ic_tab_download)) + .setContent(this)); + tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING) + .setIndicator(getString(R.string.filter_apps_running), + getResources().getDrawable(R.drawable.ic_tab_running)) + .setContent(this)); + tabHost.addTab(tabHost.newTabSpec(TAB_ALL) + .setIndicator(getString(R.string.filter_apps_all), + getResources().getDrawable(R.drawable.ic_tab_all)) + .setContent(this)); + tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD) + .setIndicator(getString(R.string.filter_apps_onsdcard), + getResources().getDrawable(R.drawable.ic_tab_sdcard)) + .setContent(this)); + tabHost.setCurrentTabByTag(defaultTabTag); + tabHost.setOnTabChangedListener(this); } + @Override protected void onDestroy() { // Persist values in cache mCache.updateCache(); @@ -1616,7 +1701,7 @@ public class ManageApplications extends ListActivity implements } @Override - public Dialog onCreateDialog(int id) { + public Dialog onCreateDialog(int id, Bundle args) { if (id == DLG_LOADING) { ProgressDialog dlg = new ProgressDialog(this); dlg.setProgressStyle(ProgressDialog.STYLE_SPINNER); @@ -1700,7 +1785,7 @@ public class ManageApplications extends ListActivity implements err = true; break; } - // Buffer length cannot be great then max. + // Buffer length cannot be greater than max. fis.read(byteBuff, 0, buffLen); String buffStr = new String(byteBuff); if (DEBUG_CACHE) { @@ -1922,8 +2007,6 @@ public class ManageApplications extends ListActivity implements .setIcon(android.R.drawable.ic_menu_sort_alphabetically); menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size) .setIcon(android.R.drawable.ic_menu_sort_by_size); - menu.add(0, FILTER_OPTIONS, 3, R.string.filter) - .setIcon(R.drawable.ic_menu_filter_settings); return true; } @@ -1932,7 +2015,6 @@ public class ManageApplications extends ListActivity implements if (mFirst) { menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA); menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE); - menu.findItem(FILTER_OPTIONS).setVisible(true); return true; } return false; @@ -1943,20 +2025,6 @@ public class ManageApplications extends ListActivity implements int menuId = item.getItemId(); if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) { sendMessageToHandler(REORDER_LIST, menuId); - } else if (menuId == FILTER_OPTIONS) { - // Pick up the selection value from the list of added choice items. - int selection = mFilterApps - MENU_OPTIONS_BASE; - if (mAlertDlg == null) { - mAlertDlg = new AlertDialog.Builder(this). - setTitle(R.string.filter_dlg_title). - setNeutralButton(R.string.cancel, this). - setSingleChoiceItems(new CharSequence[] {getText(R.string.filter_apps_all), - getText(R.string.filter_apps_running), - getText(R.string.filter_apps_third_party)}, - selection, this). - create(); - } - mAlertDlg.show(); } return true; } @@ -1973,22 +2041,41 @@ public class ManageApplications extends ListActivity implements finish(); } - public void onClick(DialogInterface dialog, int which) { + public View createTabContent(String tag) { + return mRootView; + } + + public void onTabChanged(String tabId) { int newOption; - switch (which) { - // Make sure that values of 0, 1, 2 match options all, running, third_party when - // created via the AlertDialog.Builder - case FILTER_APPS_ALL: - break; - case FILTER_APPS_RUNNING: - break; - case FILTER_APPS_THIRD_PARTY: - break; - default: + if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) { + newOption = FILTER_APPS_THIRD_PARTY; + } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) { + newOption = FILTER_APPS_RUNNING; + } else if (TAB_ALL.equalsIgnoreCase(tabId)) { + newOption = FILTER_APPS_ALL; + } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) { + newOption = FILTER_APPS_SDCARD; + } else { + // Invalid option. Do nothing return; } - newOption = which; - mAlertDlg.dismiss(); sendMessageToHandler(REORDER_LIST, newOption); } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) { + // Refresh package attributes + try { + ApplicationInfo info = mPm.getApplicationInfo(mCurrentPkgName, + PackageManager.GET_UNINSTALLED_PACKAGES); + } catch (NameNotFoundException e) { + Bundle rData = new Bundle(); + rData.putString(ATTR_PKG_NAME, mCurrentPkgName); + sendMessageToHandler(REMOVE_PKG, rData); + mCurrentPkgName = null; + } + } + } } diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index fd4a411..4de0e44 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -23,7 +23,6 @@ import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.ICheckinService; import android.os.ServiceManager; import android.os.SystemProperties; import android.text.TextUtils; @@ -53,59 +52,33 @@ public class MasterClear extends Activity { private View mFinalView; private Button mFinalButton; - /** + /** * The user has gone through the multiple confirmation, so now we go ahead * and invoke the Checkin Service to reset the device to its factory-default * state (rebooting in the process). */ private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() { public void onClick(View v) { - if (Utils.isMonkeyRunning()) { return; } - ICheckinService service = - ICheckinService.Stub.asInterface(ServiceManager.getService("checkin")); - if (service != null) { - try { - // This RPC should never return - service.masterClear(); - } catch (android.os.RemoteException e) { - // Intentionally blank - there's nothing we can do here - Log.w("MasterClear", "Unable to invoke ICheckinService.masterClear()"); - } - } else { - Log.w("MasterClear", "Unable to locate ICheckinService"); - } - - /* If we reach this point, the master clear didn't happen -- the - * service might have been unregistered with the ServiceManager, - * the RPC might have thrown an exception, or for some reason - * the implementation of masterClear() may have returned instead - * of resetting the device. - */ - new AlertDialog.Builder(MasterClear.this) - .setMessage(getText(R.string.master_clear_failed)) - .setPositiveButton(getText(android.R.string.ok), null) - .show(); + sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); + // Intent handling is asynchronous -- assume it will happen soon. } }; /** - * Keyguard validation is run using the standard {@link ConfirmLockPattern} + * Keyguard validation is run using the standard {@link ConfirmLockPattern} * component as a subactivity + * @param request the request code to be returned once confirmation finishes + * @return true if confirmation launched */ - private void runKeyguardConfirmation() { - final Intent intent = new Intent(); - intent.setClassName("com.android.settings", - "com.android.settings.ConfirmLockPattern"); - // supply header and footer text in the intent - intent.putExtra(ConfirmLockPattern.HEADER_TEXT, - getText(R.string.master_clear_gesture_prompt)); - intent.putExtra(ConfirmLockPattern.FOOTER_TEXT, - getText(R.string.master_clear_gesture_explanation)); - startActivityForResult(intent, KEYGUARD_REQUEST); + private boolean runKeyguardConfirmation(int request) { + return new ChooseLockSettingsHelper(this) + .launchConfirmationActivity(request, + getText(R.string.master_clear_gesture_prompt), + getText(R.string.master_clear_gesture_explanation)); } @Override @@ -120,6 +93,8 @@ public class MasterClear extends Activity { // confirmation prompt; otherwise, go back to the initial state. if (resultCode == Activity.RESULT_OK) { establishFinalConfirmationState(); + } else if (resultCode == Activity.RESULT_CANCELED) { + finish(); } else { establishInitialState(); } @@ -132,9 +107,7 @@ public class MasterClear extends Activity { */ private Button.OnClickListener mInitiateListener = new Button.OnClickListener() { public void onClick(View v) { - if (mLockUtils.isLockPatternEnabled()) { - runKeyguardConfirmation(); - } else { + if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) { establishFinalConfirmationState(); } } @@ -159,7 +132,7 @@ public class MasterClear extends Activity { * click in order to initiate a confirmation sequence. This method is * called from various other points in the code to reset the activity to * this base state. - * + * * <p>Reinflating views from resources is expensive and prevents us from * caching widget pointers, so we use a single-inflate pattern: we lazy- * inflate each view, caching all of the widget pointers we'll need at the @@ -184,7 +157,7 @@ public class MasterClear extends Activity { mInitialView = null; mFinalView = null; mInflater = LayoutInflater.from(this); - mLockUtils = new LockPatternUtils(getContentResolver()); + mLockUtils = new LockPatternUtils(this); establishInitialState(); } diff --git a/src/com/android/settings/MediaFormat.java b/src/com/android/settings/MediaFormat.java index e0d9af6..b78ff62 100644 --- a/src/com/android/settings/MediaFormat.java +++ b/src/com/android/settings/MediaFormat.java @@ -23,7 +23,7 @@ import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.IMountService; +import android.os.storage.IMountService; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.Environment; @@ -64,15 +64,19 @@ public class MediaFormat extends Activity { if (Utils.isMonkeyRunning()) { return; } - IMountService service = + final IMountService service = IMountService.Stub.asInterface(ServiceManager.getService("mount")); if (service != null) { - try { - service.formatMedia(Environment.getExternalStorageDirectory().toString()); - } catch (android.os.RemoteException e) { - // Intentionally blank - there's nothing we can do here - Log.w("MediaFormat", "Unable to invoke IMountService.formatMedia()"); - } + new Thread() { + public void run() { + try { + service.formatVolume(Environment.getExternalStorageDirectory().toString()); + } catch (Exception e) { + // Intentionally blank - there's nothing we can do here + Log.w("MediaFormat", "Unable to invoke IMountService.formatMedia()"); + } + } + }.start(); } else { Log.w("MediaFormat", "Unable to locate IMountService"); } @@ -84,16 +88,11 @@ public class MediaFormat extends Activity { * Keyguard validation is run using the standard {@link ConfirmLockPattern} * component as a subactivity */ - private void runKeyguardConfirmation() { - final Intent intent = new Intent(); - intent.setClassName("com.android.settings", - "com.android.settings.ConfirmLockPattern"); - // supply header and footer text in the intent - intent.putExtra(ConfirmLockPattern.HEADER_TEXT, - getText(R.string.media_format_gesture_prompt)); - intent.putExtra(ConfirmLockPattern.FOOTER_TEXT, - getText(R.string.media_format_gesture_explanation)); - startActivityForResult(intent, KEYGUARD_REQUEST); + private boolean runKeyguardConfirmation(int request) { + return new ChooseLockSettingsHelper(this) + .launchConfirmationActivity(request, + getText(R.string.media_format_gesture_prompt), + getText(R.string.media_format_gesture_explanation)); } @Override @@ -108,6 +107,8 @@ public class MediaFormat extends Activity { // confirmation prompt; otherwise, go back to the initial state. if (resultCode == Activity.RESULT_OK) { establishFinalConfirmationState(); + } else if (resultCode == Activity.RESULT_CANCELED) { + finish(); } else { establishInitialState(); } @@ -120,9 +121,7 @@ public class MediaFormat extends Activity { */ private Button.OnClickListener mInitiateListener = new Button.OnClickListener() { public void onClick(View v) { - if (mLockUtils.isLockPatternEnabled()) { - runKeyguardConfirmation(); - } else { + if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) { establishFinalConfirmationState(); } } @@ -147,7 +146,7 @@ public class MediaFormat extends Activity { * click in order to initiate a confirmation sequence. This method is * called from various other points in the code to reset the activity to * this base state. - * + * * <p>Reinflating views from resources is expensive and prevents us from * caching widget pointers, so we use a single-inflate pattern: we lazy- * inflate each view, caching all of the widget pointers we'll need at the @@ -172,7 +171,7 @@ public class MediaFormat extends Activity { mInitialView = null; mFinalView = null; mInflater = LayoutInflater.from(this); - mLockUtils = new LockPatternUtils(getContentResolver()); + mLockUtils = new LockPatternUtils(this); establishInitialState(); } diff --git a/src/com/android/settings/PrivacySettings.java b/src/com/android/settings/PrivacySettings.java index 2edb328..29deacf 100644 --- a/src/com/android/settings/PrivacySettings.java +++ b/src/com/android/settings/PrivacySettings.java @@ -18,7 +18,7 @@ package com.android.settings; import android.app.AlertDialog; import android.app.Dialog; -import android.backup.IBackupManager; +import android.app.backup.IBackupManager; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -39,24 +39,17 @@ import android.widget.TextView; * Gesture lock pattern settings. */ public class PrivacySettings extends PreferenceActivity implements - DialogInterface.OnDismissListener, DialogInterface.OnClickListener { - - private static final String PREFS_NAME = "location_prefs"; - private static final String PREFS_USE_LOCATION = "use_location"; + DialogInterface.OnClickListener { // Vendor specific - private static final String GSETTINGS_PROVIDER = "com.google.android.providers.settings"; - private static final String LOCATION_CATEGORY = "location_category"; - private static final String SETTINGS_CATEGORY = "settings_category"; - private static final String USE_LOCATION = "use_location"; - private static final String BACKUP_SETTINGS = "backup_settings"; - private static final String KEY_DONE_USE_LOCATION = "doneLocation"; - private CheckBoxPreference mUseLocation; + private static final String GSETTINGS_PROVIDER = "com.google.settings"; + private static final String BACKUP_CATEGORY = "backup_category"; + private static final String BACKUP_DATA = "backup_data"; + private static final String AUTO_RESTORE = "auto_restore"; private CheckBoxPreference mBackup; - private boolean mOkClicked; + private CheckBoxPreference mAutoRestore; private Dialog mConfirmDialog; - private static final int DIALOG_USE_LOCATION = 1; private static final int DIALOG_ERASE_BACKUP = 2; private int mDialogType; @@ -64,27 +57,16 @@ public class PrivacySettings extends PreferenceActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.privacy_settings); + final PreferenceScreen screen = getPreferenceScreen(); - mUseLocation = (CheckBoxPreference) getPreferenceScreen().findPreference(USE_LOCATION); - mBackup = (CheckBoxPreference) getPreferenceScreen().findPreference(BACKUP_SETTINGS); + mBackup = (CheckBoxPreference) screen.findPreference(BACKUP_DATA); + mAutoRestore = (CheckBoxPreference) screen.findPreference(AUTO_RESTORE); // Vendor specific - try { - if (mUseLocation != null) { - getPackageManager().getPackageInfo(GSETTINGS_PROVIDER, 0); - } - } catch (NameNotFoundException nnfe) { - getPreferenceScreen().removePreference(findPreference(LOCATION_CATEGORY)); - getPreferenceScreen().removePreference(findPreference(SETTINGS_CATEGORY)); + if (getPackageManager().resolveContentProvider(GSETTINGS_PROVIDER, 0) == null) { + screen.removePreference(findPreference(BACKUP_CATEGORY)); } updateToggles(); - - boolean doneUseLocation = savedInstanceState == null - ? false : savedInstanceState.getBoolean(KEY_DONE_USE_LOCATION, true); - if (!doneUseLocation && (getIntent().getBooleanExtra("SHOW_USE_LOCATION", false) - || savedInstanceState != null)) { - showUseLocationDialog(true); - } } @Override @@ -98,63 +80,32 @@ public class PrivacySettings extends PreferenceActivity implements } @Override - public void onSaveInstanceState(Bundle icicle) { - if (mConfirmDialog != null && mConfirmDialog.isShowing() - && mDialogType == DIALOG_USE_LOCATION) { - icicle.putBoolean(KEY_DONE_USE_LOCATION, false); - } - super.onSaveInstanceState(icicle); - } - - @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - if (preference == mUseLocation) { - //normally called on the toggle click - if (mUseLocation.isChecked()) { - showUseLocationDialog(false); - } else { - updateUseLocation(); - } - } else if (preference == mBackup) { + if (preference == mBackup) { if (!mBackup.isChecked()) { showEraseBackupDialog(); } else { setBackupEnabled(true); } + } else if (preference == mAutoRestore) { + IBackupManager bm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + if (bm != null) { + // TODO: disable via the backup manager interface + boolean curState = mAutoRestore.isChecked(); + try { + bm.setAutoRestore(curState); + } catch (RemoteException e) { + mAutoRestore.setChecked(!curState); + } + } } return false; } - private void showUseLocationDialog(boolean force) { - // Show a warning to the user that location data will be shared - mOkClicked = false; - if (force) { - mUseLocation.setChecked(true); - } - - if (hasAgreedToUseLocation()) { - updateUseLocation(); - return; - } - - mDialogType = DIALOG_USE_LOCATION; - CharSequence msg = getResources().getText(R.string.use_location_warning_message); - mConfirmDialog = new AlertDialog.Builder(this).setMessage(msg) - .setTitle(R.string.use_location_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton(R.string.agree, this) - .setNegativeButton(R.string.disagree, this) - .show(); - ((TextView)mConfirmDialog.findViewById(android.R.id.message)) - .setMovementMethod(LinkMovementMethod.getInstance()); - mConfirmDialog.setOnDismissListener(this); - } - private void showEraseBackupDialog() { - // Show a warning to the user that location data will be shared - mOkClicked = false; mBackup.setChecked(true); mDialogType = DIALOG_ERASE_BACKUP; @@ -165,7 +116,6 @@ public class PrivacySettings extends PreferenceActivity implements .setPositiveButton(android.R.string.ok, this) .setNegativeButton(android.R.string.cancel, this) .show(); - mConfirmDialog.setOnDismissListener(this); } /* @@ -173,72 +123,31 @@ public class PrivacySettings extends PreferenceActivity implements */ private void updateToggles() { ContentResolver res = getContentResolver(); - mUseLocation.setChecked(Settings.Secure.getInt(res, - Settings.Secure.USE_LOCATION_FOR_SERVICES, 2) == 1); - mBackup.setChecked(Settings.Secure.getInt(res, - Settings.Secure.BACKUP_ENABLED, 0) == 1); - } - private void updateUseLocation() { - boolean use = mUseLocation.isChecked(); - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.USE_LOCATION_FOR_SERVICES, use ? 1 : 0); + final boolean backupEnabled = Settings.Secure.getInt(res, + Settings.Secure.BACKUP_ENABLED, 0) == 1; + mBackup.setChecked(backupEnabled); + + mAutoRestore.setChecked(Settings.Secure.getInt(res, + Settings.Secure.BACKUP_AUTO_RESTORE, 1) == 1); + mAutoRestore.setEnabled(backupEnabled); } public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { //updateProviders(); - mOkClicked = true; - if (mDialogType == DIALOG_USE_LOCATION) { - setAgreedToUseLocation(true); - } else if (mDialogType == DIALOG_ERASE_BACKUP) { + if (mDialogType == DIALOG_ERASE_BACKUP) { setBackupEnabled(false); } } else { - if (mDialogType == DIALOG_USE_LOCATION) { - // Reset the toggle - mUseLocation.setChecked(false); - } else if (mDialogType == DIALOG_ERASE_BACKUP) { + if (mDialogType == DIALOG_ERASE_BACKUP) { mBackup.setChecked(true); + mAutoRestore.setEnabled(true); } } - updateUseLocation(); mDialogType = 0; } - public void onDismiss(DialogInterface dialog) { - // Assuming that onClick gets called first - if (!mOkClicked) { - if (mDialogType == DIALOG_USE_LOCATION) { - mUseLocation.setChecked(false); - } - } - } - - /** - * Checks if the user has agreed to the dialog in the past. - */ - private boolean hasAgreedToUseLocation() { - SharedPreferences sp = getSharedPreferences(PREFS_NAME, 0); - if (sp == null) { - return false; - } - return sp.getBoolean(PREFS_USE_LOCATION, false); - } - - /** - * Notes that the user has agreed to the dialog and won't need to be prompted in the - * future. - */ - private void setAgreedToUseLocation(boolean agreed) { - if (agreed) { - SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean(PREFS_USE_LOCATION, true); - editor.commit(); - } - } - /** * Informs the BackupManager of a change in backup state - if backup is disabled, * the data on the server will be erased. @@ -252,9 +161,11 @@ public class PrivacySettings extends PreferenceActivity implements bm.setBackupEnabled(enable); } catch (RemoteException e) { mBackup.setChecked(!enable); + mAutoRestore.setEnabled(!enable); return; } } mBackup.setChecked(enable); + mAutoRestore.setEnabled(enable); } } diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java index ce236fd..f0fcdd7 100644 --- a/src/com/android/settings/RadioInfo.java +++ b/src/com/android/settings/RadioInfo.java @@ -20,6 +20,8 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.SharedPreferences; import android.content.res.Resources; import android.net.Uri; @@ -57,8 +59,7 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.PhoneStateIntentReceiver; import com.android.internal.telephony.TelephonyProperties; -import com.android.internal.telephony.gsm.GSMPhone; -import com.android.internal.telephony.gsm.PdpConnection; +import com.android.internal.telephony.gsm.GsmDataConnection; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; @@ -72,6 +73,8 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import android.util.Log; + public class RadioInfo extends Activity { private final String TAG = "phone"; @@ -83,8 +86,6 @@ public class RadioInfo extends Activity { private static final int EVENT_QUERY_PREFERRED_TYPE_DONE = 1000; private static final int EVENT_SET_PREFERRED_TYPE_DONE = 1001; private static final int EVENT_QUERY_NEIGHBORING_CIDS_DONE = 1002; - private static final int EVENT_SET_QXDMLOG_DONE = 1003; - private static final int EVENT_SET_CIPHER_DONE = 1004; private static final int EVENT_QUERY_SMSC_DONE = 1005; private static final int EVENT_UPDATE_SMSC_DONE = 1006; @@ -94,7 +95,9 @@ public class RadioInfo extends Activity { private static final int MENU_ITEM_VIEW_SDN = 3; private static final int MENU_ITEM_GET_PDP_LIST = 4; private static final int MENU_ITEM_TOGGLE_DATA = 5; - private static final int MENU_ITEM_TOGGLE_DATA_ON_BOOT = 6; + + static final String ENABLE_DATA_STR = "Enable data connection"; + static final String DISABLE_DATA_STR = "Disable data connection"; private TextView mDeviceId; //DeviceId is the IMEI in GSM and the MEID in CDMA private TextView number; @@ -119,16 +122,14 @@ public class RadioInfo extends Activity { private TextView mPingIpAddr; private TextView mPingHostname; private TextView mHttpClientTest; - private TextView cipherState; private TextView dnsCheckState; private EditText smsc; private Button radioPowerButton; - private Button qxdmLogButton; - private Button cipherToggleButton; private Button dnsCheckToggleButton; private Button pingTestButton; private Button updateSmscButton; private Button refreshSmscButton; + private Button oemInfoButton; private Spinner preferredNetworkType; private TelephonyManager mTelephonyManager; @@ -136,11 +137,6 @@ public class RadioInfo extends Activity { private PhoneStateIntentReceiver mPhoneStateReceiver; private INetStatService netstat; - private OemCommands mOem = null; - private boolean mQxdmLogEnabled; - // The requested cipher state - private boolean mCipherOn; - private String mPingIpAddrResult; private String mPingHostnameResult; private String mHttpClientTestResult; @@ -220,22 +216,6 @@ public class RadioInfo extends Activity { mNeighboringCids.setText("unknown"); } break; - case EVENT_SET_QXDMLOG_DONE: - ar= (AsyncResult) msg.obj; - if (ar.exception == null) { - mQxdmLogEnabled = !mQxdmLogEnabled; - - updateQxdmState(mQxdmLogEnabled); - displayQxdmEnableResult(); - } - break; - case EVENT_SET_CIPHER_DONE: - ar= (AsyncResult) msg.obj; - if (ar.exception == null) { - setCiphPref(mCipherOn); - } - updateCiphState(); - break; case EVENT_QUERY_SMSC_DONE: ar= (AsyncResult) msg.obj; if (ar.exception != null) { @@ -258,116 +238,6 @@ public class RadioInfo extends Activity { } }; - static private class OemCommands { - - public static final int OEM_QXDM_SDLOG_DEFAULT_FILE_SIZE = 32; - public static final int OEM_QXDM_SDLOG_DEFAULT_MASK = 0; - public static final int OEM_QXDM_SDLOG_DEFAULT_MAX_INDEX = 8; - - static final int SIZE_OF_INT = 4; - static final int OEM_FEATURE_ENABLE = 1; - static final int OEM_FEATURE_DISABLE = 0; - static final int OEM_SIMPE_FEAUTURE_LEN = 1; - - static final int OEM_QXDM_SDLOG_FUNCTAG = 0x00010000; - static final int OEM_QXDM_SDLOG_LEN = 4; - static final int OEM_PS_AUTO_ATTACH_FUNCTAG = 0x00020000; - static final int OEM_CIPHERING_FUNCTAG = 0x00020001; - - /** - * The OEM interface to store QXDM to SD. - * - * To start/stop logging QXDM logs to SD card, use tag - * OEM_RIL_HOOK_QXDM_SD_LOG_SETUP 0x00010000 - * - * "data" is a const oem_ril_hook_qxdm_sdlog_setup_data_st * - * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->head.func_tag - * should be OEM_RIL_HOOK_QXDM_SD_LOG_SETUP - * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->head.len - * should be "sizeof(unsigned int) * 4" - * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->mode - * could be 0 for 'stop logging', or 1 for 'start logging' - * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->log_file_size - * will assign the size of each log file, and it could be a value between - * 1 and 512 (in megabytes, default value is recommended to set as 32). - * This value will be ignored when mode == 0. - * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->log_mask will - * assign the rule to filter logs, and it is a bitmask (bit0 is for MsgAll, - * bit1 is for LogAll, and bit2 is for EventAll) recommended to be set as 0 - * by default. This value will be ignored when mode == 0. - * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->log_max_fileindex - * set the how many logfiles will storted before roll over. This value will - * be ignored when mode == 0. - * - * "response" is NULL - * - * typedef struct _oem_ril_hook_raw_head_st { - * unsigned int func_tag; - * unsigned int len; - * } oem_ril_hook_raw_head_st; - * - * typedef struct _oem_ril_hook_qxdm_sdlog_setup_data_st { - * oem_ril_hook_raw_head_st head; - * unsigned int mode; - * unsigned int log_file_size; - * unsigned int log_mask; - * unsigned int log_max_fileindex; - * } oem_ril_hook_qxdm_sdlog_setup_data_st; - * - * @param enable set true to start logging QXDM in SD card - * @param fileSize is the log file size in MB - * @param mask is the log mask to filter - * @param maxIndex is the maximum roll-over file number - * @return byteArray to use in RIL RAW command - */ - byte[] getQxdmSdlogData(boolean enable, int fileSize, int mask, int maxIndex) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - try { - writeIntLittleEndian(dos, OEM_QXDM_SDLOG_FUNCTAG); - writeIntLittleEndian(dos, OEM_QXDM_SDLOG_LEN * SIZE_OF_INT); - writeIntLittleEndian(dos, enable ? - OEM_FEATURE_ENABLE : OEM_FEATURE_DISABLE); - writeIntLittleEndian(dos, fileSize); - writeIntLittleEndian(dos, mask); - writeIntLittleEndian(dos, maxIndex); - } catch (IOException e) { - return null; - } - return bos.toByteArray(); - } - - byte[] getPsAutoAttachData(boolean enable) { - return getSimpleFeatureData(OEM_PS_AUTO_ATTACH_FUNCTAG, enable); - } - - byte[] getCipheringData(boolean enable) { - return getSimpleFeatureData(OEM_CIPHERING_FUNCTAG, enable); - } - - private byte[] getSimpleFeatureData(int tag, boolean enable) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(bos); - try { - writeIntLittleEndian(dos, tag); - writeIntLittleEndian(dos, OEM_SIMPE_FEAUTURE_LEN * SIZE_OF_INT); - writeIntLittleEndian(dos, enable ? - OEM_FEATURE_ENABLE : OEM_FEATURE_DISABLE); - } catch (IOException e) { - return null; - } - return bos.toByteArray(); - } - - private void writeIntLittleEndian(DataOutputStream dos, int val) - throws IOException { - dos.writeByte(val); - dos.writeByte(val >> 8); - dos.writeByte(val >> 16); - dos.writeByte(val >> 24); - } - } - @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -398,7 +268,6 @@ public class RadioInfo extends Activity { sentSinceReceived = (TextView) findViewById(R.id.sentSinceReceived); sent = (TextView) findViewById(R.id.sent); received = (TextView) findViewById(R.id.received); - cipherState = (TextView) findViewById(R.id.ciphState); smsc = (EditText) findViewById(R.id.smsc); dnsCheckState = (TextView) findViewById(R.id.dnsCheckState); @@ -416,11 +285,6 @@ public class RadioInfo extends Activity { radioPowerButton = (Button) findViewById(R.id.radio_power); radioPowerButton.setOnClickListener(mPowerButtonHandler); - qxdmLogButton = (Button) findViewById(R.id.qxdm_log); - qxdmLogButton.setOnClickListener(mQxdmButtonHandler); - - cipherToggleButton = (Button) findViewById(R.id.ciph_toggle); - cipherToggleButton.setOnClickListener(mCipherButtonHandler); pingTestButton = (Button) findViewById(R.id.ping_test); pingTestButton.setOnClickListener(mPingButtonHandler); updateSmscButton = (Button) findViewById(R.id.update_smsc); @@ -430,14 +294,20 @@ public class RadioInfo extends Activity { dnsCheckToggleButton = (Button) findViewById(R.id.dns_check_toggle); dnsCheckToggleButton.setOnClickListener(mDnsCheckButtonHandler); + oemInfoButton = (Button) findViewById(R.id.oem_info); + oemInfoButton.setOnClickListener(mOemInfoButtonHandler); + PackageManager pm = getPackageManager(); + Intent oemInfoIntent = new Intent("com.android.settings.OEM_RADIO_INFO"); + List<ResolveInfo> oemInfoIntentList = pm.queryIntentActivities(oemInfoIntent, 0); + if (oemInfoIntentList.size() == 0) { + oemInfoButton.setEnabled(false); + } + mPhoneStateReceiver = new PhoneStateIntentReceiver(this, mHandler); mPhoneStateReceiver.notifySignalStrength(EVENT_SIGNAL_STRENGTH_CHANGED); mPhoneStateReceiver.notifyServiceState(EVENT_SERVICE_STATE_CHANGED); mPhoneStateReceiver.notifyPhoneCallState(EVENT_PHONE_STATE_CHANGED); - updateQxdmState(null); - mOem = new OemCommands(); - phone.getPreferredNetworkType( mHandler.obtainMessage(EVENT_QUERY_PREFERRED_TYPE_DONE)); phone.getNeighboringCids( @@ -462,9 +332,7 @@ public class RadioInfo extends Activity { updateDataStats(); updateDataStats2(); updatePowerState(); - updateQxdmState(null); updateProperties(); - updateCiphState(); updateDnsCheckState(); Log.i(TAG, "[RadioInfo] onResume: register phone & data intents"); @@ -502,14 +370,10 @@ public class RadioInfo extends Activity { menu.add(1, MENU_ITEM_GET_PDP_LIST, 0, R.string.radioInfo_menu_getPDP).setOnMenuItemClickListener(mGetPdpList); menu.add(1, MENU_ITEM_TOGGLE_DATA, - 0, R.string.radioInfo_menu_disableData).setOnMenuItemClickListener(mToggleData); - menu.add(1, MENU_ITEM_TOGGLE_DATA_ON_BOOT, - 0, R.string.radioInfo_menu_disableDataOnBoot).setOnMenuItemClickListener( - mToggleDataOnBoot); + 0, DISABLE_DATA_STR).setOnMenuItemClickListener(mToggleData); return true; } - @Override public boolean onPrepareOptionsMenu(Menu menu) { // Get the TOGGLE DATA menu item in the right state. @@ -520,26 +384,16 @@ public class RadioInfo extends Activity { switch (state) { case TelephonyManager.DATA_CONNECTED: case TelephonyManager.DATA_SUSPENDED: - item.setTitle(R.string.radioInfo_menu_disableData); + item.setTitle(DISABLE_DATA_STR); break; case TelephonyManager.DATA_DISCONNECTED: - item.setTitle(R.string.radioInfo_menu_enableData); + item.setTitle(ENABLE_DATA_STR); break; default: visible = false; break; } item.setVisible(visible); - - // Get the toggle-data-on-boot menu item in the right state. - item = menu.findItem(MENU_ITEM_TOGGLE_DATA_ON_BOOT); - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext()); - boolean value = sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false); - if (value) { - item.setTitle(R.string.radioInfo_menu_enableDataOnBoot); - } else { - item.setTitle(R.string.radioInfo_menu_disableDataOnBoot); - } return true; } @@ -554,42 +408,6 @@ public class RadioInfo extends Activity { radioPowerButton.setText(buttonText); } - private void updateQxdmState(Boolean newQxdmStatus) { - SharedPreferences sp = - PreferenceManager.getDefaultSharedPreferences(phone.getContext()); - mQxdmLogEnabled = sp.getBoolean("qxdmstatus", false); - // This is called from onCreate, onResume, and the handler when the status - // is updated. - if (newQxdmStatus != null) { - SharedPreferences.Editor editor = sp.edit(); - editor.putBoolean("qxdmstatus", newQxdmStatus); - editor.commit(); - mQxdmLogEnabled = newQxdmStatus; - } - - String buttonText = mQxdmLogEnabled ? - getString(R.string.turn_off_qxdm) : - getString(R.string.turn_on_qxdm); - qxdmLogButton.setText(buttonText); - } - - private void setCiphPref(boolean value) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext()); - SharedPreferences.Editor editor = sp.edit(); - editor.putBoolean(GSMPhone.CIPHERING_KEY, value); - editor.commit(); - } - - private boolean getCiphPref() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext()); - boolean ret = sp.getBoolean(GSMPhone.CIPHERING_KEY, true); - return ret; - } - - private void updateCiphState() { - cipherState.setText(getCiphPref() ? "Ciphering ON" : "Ciphering OFF"); - } - private void updateDnsCheckState() { dnsCheckState.setText(phone.isDnsCheckDisabled() ? "0.0.0.0 allowed" :"0.0.0.0 not allowed"); @@ -942,8 +760,8 @@ public class RadioInfo extends Activity { List<DataConnection> dcs = phone.getCurrentDataConnectionList(); for (DataConnection dc : dcs) { - sb.append(" State: ").append(dc.getState().toString()).append("\n"); - if (dc.getState().isActive()) { + sb.append(" State: ").append(dc.getStateAsString()).append("\n"); + if (dc.isActive()) { long timeElapsed = (System.currentTimeMillis() - dc.getConnectionTime())/1000; sb.append(" connected at ") @@ -951,8 +769,8 @@ public class RadioInfo extends Activity { .append(" and elapsed ") .append(DateUtils.formatElapsedTime(timeElapsed)); - if (dc instanceof PdpConnection) { - PdpConnection pdp = (PdpConnection)dc; + if (dc instanceof GsmDataConnection) { + GsmDataConnection pdp = (GsmDataConnection)dc; sb.append("\n to ") .append(pdp.getApn().toString()); } @@ -966,14 +784,14 @@ public class RadioInfo extends Activity { if (dns != null) { sb.append("\ndns: ").append(dns[0]).append(", ").append(dns[1]); } - } else if (dc.getState().isInactive()) { + } else if (dc.isInactive()) { sb.append(" disconnected with last try at ") .append(DateUtils.timeString(dc.getLastFailTime())) .append("\n fail because ") .append(dc.getLastFailCause().toString()); } else { - if (dc instanceof PdpConnection) { - PdpConnection pdp = (PdpConnection)dc; + if (dc instanceof GsmDataConnection) { + GsmDataConnection pdp = (GsmDataConnection)dc; sb.append(" is connecting to ") .append(pdp.getApn().toString()); } else { @@ -987,19 +805,6 @@ public class RadioInfo extends Activity { disconnects.setText(sb.toString()); } - private void displayQxdmEnableResult() { - String status = mQxdmLogEnabled ? "Start QXDM Log" : "Stop QXDM Log"; - - new AlertDialog.Builder(this).setMessage(status).show(); - - mHandler.postDelayed( - new Runnable() { - public void run() { - finish(); - } - }, 2000); - } - private MenuItem.OnMenuItemClickListener mViewADNCallback = new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { Intent intent = new Intent(Intent.ACTION_VIEW); @@ -1046,24 +851,18 @@ public class RadioInfo extends Activity { } }; - private void toggleDataDisabledOnBoot() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(phone.getContext()); - SharedPreferences.Editor editor = sp.edit(); - boolean value = sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false); - editor.putBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, !value); - byte[] data = mOem.getPsAutoAttachData(value); - if (data == null) { - // don't commit - return; + private MenuItem.OnMenuItemClickListener mGetPdpList = new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + phone.getDataCallList(null); + return true; } + }; - editor.commit(); - phone.invokeOemRilRequestRaw(data, null); - } - - private MenuItem.OnMenuItemClickListener mToggleDataOnBoot = new MenuItem.OnMenuItemClickListener() { + private MenuItem.OnMenuItemClickListener mSelectBandCallback = new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - toggleDataDisabledOnBoot(); + Intent intent = new Intent(); + intent.setClass(RadioInfo.this, BandMode.class); + startActivity(intent); return true; } }; @@ -1086,22 +885,6 @@ public class RadioInfo extends Activity { } }; - private MenuItem.OnMenuItemClickListener mGetPdpList = new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - phone.getDataCallList(null); - return true; - } - }; - - private MenuItem.OnMenuItemClickListener mSelectBandCallback = new MenuItem.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - Intent intent = new Intent(); - intent.setClass(RadioInfo.this, BandMode.class); - startActivity(intent); - return true; - } - }; - OnClickListener mPowerButtonHandler = new OnClickListener() { public void onClick(View v) { //log("toggle radio power: currently " + (isRadioOn()?"on":"off")); @@ -1109,24 +892,23 @@ public class RadioInfo extends Activity { } }; - OnClickListener mCipherButtonHandler = new OnClickListener() { + OnClickListener mDnsCheckButtonHandler = new OnClickListener() { public void onClick(View v) { - mCipherOn = !getCiphPref(); - byte[] data = mOem.getCipheringData(mCipherOn); - - if (data == null) - return; - - cipherState.setText("Setting..."); - phone.invokeOemRilRequestRaw(data, - mHandler.obtainMessage(EVENT_SET_CIPHER_DONE)); + phone.disableDnsCheck(!phone.isDnsCheckDisabled()); + updateDnsCheckState(); } }; - OnClickListener mDnsCheckButtonHandler = new OnClickListener() { + OnClickListener mOemInfoButtonHandler = new OnClickListener() { public void onClick(View v) { - phone.disableDnsCheck(!phone.isDnsCheckDisabled()); - updateDnsCheckState(); + Intent intent = new Intent("com.android.settings.OEM_RADIO_INFO"); + try { + startActivity(intent); + } catch (android.content.ActivityNotFoundException ex) { + Log.d(TAG, "OEM-specific Info/Settings Activity Not Found : " + ex); + // If the activity does not exist, there are no OEM + // settings, and so we can just do nothing... + } } }; @@ -1150,22 +932,6 @@ public class RadioInfo extends Activity { } }; - OnClickListener mQxdmButtonHandler = new OnClickListener() { - public void onClick(View v) { - byte[] data = mOem.getQxdmSdlogData( - !mQxdmLogEnabled, - mOem.OEM_QXDM_SDLOG_DEFAULT_FILE_SIZE, - mOem.OEM_QXDM_SDLOG_DEFAULT_MASK, - mOem.OEM_QXDM_SDLOG_DEFAULT_MAX_INDEX); - - if (data == null) - return; - - phone.invokeOemRilRequestRaw(data, - mHandler.obtainMessage(EVENT_SET_QXDMLOG_DONE)); - } - }; - AdapterView.OnItemSelectedListener mPreferredNetworkHandler = new AdapterView.OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View v, int pos, long id) { diff --git a/src/com/android/settings/RingerVolumePreference.java b/src/com/android/settings/RingerVolumePreference.java index 0d01965..3ecd819 100644 --- a/src/com/android/settings/RingerVolumePreference.java +++ b/src/com/android/settings/RingerVolumePreference.java @@ -39,35 +39,49 @@ public class RingerVolumePreference extends VolumePreference implements private static final String TAG = "RingerVolumePreference"; private CheckBox mNotificationsUseRingVolumeCheckbox; - private SeekBarVolumizer mNotificationSeekBarVolumizer; + private SeekBarVolumizer [] mSeekBarVolumizer; + private static final int[] SEEKBAR_ID = new int[] { + R.id.notification_volume_seekbar, + R.id.media_volume_seekbar, + R.id.alarm_volume_seekbar + }; + private static final int[] SEEKBAR_TYPE = new int[] { + AudioManager.STREAM_NOTIFICATION, + AudioManager.STREAM_MUSIC, + AudioManager.STREAM_ALARM + }; + //private SeekBarVolumizer mNotificationSeekBarVolumizer; private TextView mNotificationVolumeTitle; - + public RingerVolumePreference(Context context, AttributeSet attrs) { super(context, attrs); // The always visible seekbar is for ring volume setStreamType(AudioManager.STREAM_RING); - + setDialogLayoutResource(R.layout.preference_dialog_ringervolume); + setDialogIcon(R.drawable.ic_settings_sound); + + mSeekBarVolumizer = new SeekBarVolumizer[SEEKBAR_ID.length]; } @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); - + + for (int i = 0; i < SEEKBAR_ID.length; i++) { + SeekBar seekBar = (SeekBar) view.findViewById(SEEKBAR_ID[i]); + mSeekBarVolumizer[i] = new SeekBarVolumizer(getContext(), seekBar, + SEEKBAR_TYPE[i]); + } + + mNotificationVolumeTitle = (TextView) view.findViewById(R.id.notification_volume_title); mNotificationsUseRingVolumeCheckbox = (CheckBox) view.findViewById(R.id.same_notification_volume); mNotificationsUseRingVolumeCheckbox.setOnCheckedChangeListener(this); mNotificationsUseRingVolumeCheckbox.setChecked(Settings.System.getInt( getContext().getContentResolver(), Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1); - - final SeekBar seekBar = (SeekBar) view.findViewById(R.id.notification_volume_seekbar); - mNotificationSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, - AudioManager.STREAM_NOTIFICATION); - - mNotificationVolumeTitle = (TextView) view.findViewById(R.id.notification_volume_title); - setNotificationVolumeVisibility(!mNotificationsUseRingVolumeCheckbox.isChecked()); } @@ -75,10 +89,11 @@ public class RingerVolumePreference extends VolumePreference implements protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); - if (!positiveResult && mNotificationSeekBarVolumizer != null) { - mNotificationSeekBarVolumizer.revertVolume(); - } - + if (!positiveResult) { + for (SeekBarVolumizer vol : mSeekBarVolumizer) { + if (vol != null) vol.revertVolume(); + } + } cleanup(); } @@ -107,29 +122,30 @@ public class RingerVolumePreference extends VolumePreference implements @Override protected void onSampleStarting(SeekBarVolumizer volumizer) { super.onSampleStarting(volumizer); - - if (mNotificationSeekBarVolumizer != null && volumizer != mNotificationSeekBarVolumizer) { - mNotificationSeekBarVolumizer.stopSample(); + for (SeekBarVolumizer vol : mSeekBarVolumizer) { + if (vol != null && vol != volumizer) vol.stopSample(); } } private void setNotificationVolumeVisibility(boolean visible) { - if (mNotificationSeekBarVolumizer != null) { - mNotificationSeekBarVolumizer.getSeekBar().setVisibility( + if (mSeekBarVolumizer[0] != null) { + mSeekBarVolumizer[0].getSeekBar().setVisibility( visible ? View.VISIBLE : View.GONE); - mNotificationVolumeTitle.setVisibility(visible ? View.VISIBLE : View.GONE); } + mNotificationVolumeTitle.setVisibility(visible ? View.VISIBLE : View.GONE); } - + private void cleanup() { - if (mNotificationSeekBarVolumizer != null) { - Dialog dialog = getDialog(); - if (dialog != null && dialog.isShowing()) { - // Stopped while dialog was showing, revert changes - mNotificationSeekBarVolumizer.revertVolume(); + for (int i = 0; i < SEEKBAR_ID.length; i++) { + if (mSeekBarVolumizer[i] != null) { + Dialog dialog = getDialog(); + if (dialog != null && dialog.isShowing()) { + // Stopped while dialog was showing, revert changes + mSeekBarVolumizer[i].revertVolume(); + } + mSeekBarVolumizer[i].stop(); + mSeekBarVolumizer[i] = null; } - mNotificationSeekBarVolumizer.stop(); - mNotificationSeekBarVolumizer = null; } } @@ -142,8 +158,12 @@ public class RingerVolumePreference extends VolumePreference implements } final SavedState myState = new SavedState(superState); - if (mNotificationSeekBarVolumizer != null) { - mNotificationSeekBarVolumizer.onSaveInstanceState(myState.getVolumeStore()); + VolumeStore[] volumeStore = myState.getVolumeStore(SEEKBAR_ID.length); + for (int i = 0; i < SEEKBAR_ID.length; i++) { + SeekBarVolumizer vol = mSeekBarVolumizer[i]; + if (vol != null) { + vol.onSaveInstanceState(volumeStore[i]); + } } return myState; } @@ -158,28 +178,44 @@ public class RingerVolumePreference extends VolumePreference implements SavedState myState = (SavedState) state; super.onRestoreInstanceState(myState.getSuperState()); - if (mNotificationSeekBarVolumizer != null) { - mNotificationSeekBarVolumizer.onRestoreInstanceState(myState.getVolumeStore()); + VolumeStore[] volumeStore = myState.getVolumeStore(SEEKBAR_ID.length); + for (int i = 0; i < SEEKBAR_ID.length; i++) { + SeekBarVolumizer vol = mSeekBarVolumizer[i]; + if (vol != null) { + vol.onRestoreInstanceState(volumeStore[i]); + } } } private static class SavedState extends BaseSavedState { - VolumeStore mVolumeStore = new VolumeStore(); + VolumeStore [] mVolumeStore; public SavedState(Parcel source) { super(source); - mVolumeStore.volume = source.readInt(); - mVolumeStore.originalVolume = source.readInt(); + mVolumeStore = new VolumeStore[SEEKBAR_ID.length]; + for (int i = 0; i < SEEKBAR_ID.length; i++) { + mVolumeStore[i] = new VolumeStore(); + mVolumeStore[i].volume = source.readInt(); + mVolumeStore[i].originalVolume = source.readInt(); + } } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeInt(mVolumeStore.volume); - dest.writeInt(mVolumeStore.originalVolume); + for (int i = 0; i < SEEKBAR_ID.length; i++) { + dest.writeInt(mVolumeStore[i].volume); + dest.writeInt(mVolumeStore[i].originalVolume); + } } - VolumeStore getVolumeStore() { + VolumeStore[] getVolumeStore(int count) { + if (mVolumeStore == null || mVolumeStore.length != count) { + mVolumeStore = new VolumeStore[count]; + for (int i = 0; i < count; i++) { + mVolumeStore[i] = new VolumeStore(); + } + } return mVolumeStore; } diff --git a/src/com/android/settings/RunningServices.java b/src/com/android/settings/RunningServices.java index 6c11ea0..e67adf0 100644 --- a/src/com/android/settings/RunningServices.java +++ b/src/com/android/settings/RunningServices.java @@ -35,9 +35,14 @@ import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import android.os.Bundle; import android.os.Debug; import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; @@ -53,11 +58,14 @@ import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import java.io.FileInputStream; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -72,6 +80,7 @@ public class RunningServices extends ListActivity static final int MSG_UPDATE_TIMES = 1; static final int MSG_UPDATE_CONTENTS = 2; + static final int MSG_REFRESH_UI = 3; static final long TIME_UPDATE_DELAY = 1000; static final long CONTENTS_UPDATE_DELAY = 2000; @@ -93,13 +102,16 @@ public class RunningServices extends ListActivity int mProcessBgColor; + LinearColorBar mColorBar; TextView mBackgroundProcessText; TextView mForegroundProcessText; int mLastNumBackgroundProcesses = -1; int mLastNumForegroundProcesses = -1; + int mLastNumServiceProcesses = -1; long mLastBackgroundProcessMemory = -1; long mLastForegroundProcessMemory = -1; + long mLastServiceProcessMemory = -1; long mLastAvailMemory = -1; Dialog mCurDialog; @@ -179,6 +191,11 @@ public class RunningServices extends ListActivity int mRunningSeq; ActivityManager.RunningAppProcessInfo mRunningProcessInfo; + // Purely for sorting. + boolean mIsSystem; + boolean mIsStarted; + long mActiveSince; + public ProcessItem(Context context, int uid, String processName) { super(true); mDescription = context.getResources().getString( @@ -372,26 +389,56 @@ public class RunningServices extends ListActivity } } + static class ServiceProcessComparator implements Comparator<ProcessItem> { + public int compare(ProcessItem object1, ProcessItem object2) { + if (object1.mIsStarted != object2.mIsStarted) { + // Non-started processes go last. + return object1.mIsStarted ? -1 : 1; + } + if (object1.mIsSystem != object2.mIsSystem) { + // System processes go below non-system. + return object1.mIsSystem ? 1 : -1; + } + if (object1.mActiveSince != object2.mActiveSince) { + // Remaining ones are sorted with the longest running + // services last. + return (object1.mActiveSince > object2.mActiveSince) ? -1 : 1; + } + return 0; + } + } + static class State { final SparseArray<HashMap<String, ProcessItem>> mProcesses = new SparseArray<HashMap<String, ProcessItem>>(); final SparseArray<ProcessItem> mActiveProcesses = new SparseArray<ProcessItem>(); + final ServiceProcessComparator mServiceProcessComparator + = new ServiceProcessComparator(); // Temporary for finding process dependencies. final SparseArray<ProcessItem> mRunningProcesses = new SparseArray<ProcessItem>(); - final ArrayList<BaseItem> mItems = new ArrayList<BaseItem>(); final ArrayList<ProcessItem> mProcessItems = new ArrayList<ProcessItem>(); final ArrayList<ProcessItem> mAllProcessItems = new ArrayList<ProcessItem>(); int mSequence = 0; + // ----- following protected by mLock ----- + + // Lock for protecting the state that will be shared between the + // background update thread and the UI thread. + final Object mLock = new Object(); + + ArrayList<BaseItem> mItems = new ArrayList<BaseItem>(); + int mNumBackgroundProcesses; long mBackgroundProcessMemory; int mNumForegroundProcesses; long mForegroundProcessMemory; + int mNumServiceProcesses; + long mServiceProcessMemory; boolean update(Context context, ActivityManager am) { final PackageManager pm = context.getPackageManager(); @@ -547,35 +594,65 @@ public class RunningServices extends ListActivity } if (changed) { - mItems.clear(); - mProcessItems.clear(); + // First determine an order for the services. + ArrayList<ProcessItem> sortedProcesses = new ArrayList<ProcessItem>(); for (int i=0; i<mProcesses.size(); i++) { for (ProcessItem pi : mProcesses.valueAt(i).values()) { - pi.mNeedDivider = false; - // First add processes we are dependent on. - pi.addDependentProcesses(mItems, mProcessItems); - // And add the process itself. - mItems.add(pi); - if (pi.mPid > 0) { - mProcessItems.add(pi); - } - // And finally the services running in it. - boolean needDivider = false; + pi.mIsSystem = false; + pi.mIsStarted = true; + pi.mActiveSince = Long.MAX_VALUE; for (ServiceItem si : pi.mServices.values()) { - si.mNeedDivider = needDivider; - needDivider = true; - mItems.add(si); + if (si.mServiceInfo != null + && (si.mServiceInfo.applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0) { + pi.mIsSystem = true; + } + if (si.mRunningService != null + && si.mRunningService.clientLabel != 0) { + pi.mIsStarted = false; + if (pi.mActiveSince > si.mRunningService.activeSince) { + pi.mActiveSince = si.mRunningService.activeSince; + } + } } + sortedProcesses.add(pi); } } + + Collections.sort(sortedProcesses, mServiceProcessComparator); + + ArrayList<BaseItem> newItems = new ArrayList<BaseItem>(); + mProcessItems.clear(); + for (int i=0; i<sortedProcesses.size(); i++) { + ProcessItem pi = sortedProcesses.get(i); + pi.mNeedDivider = false; + // First add processes we are dependent on. + pi.addDependentProcesses(newItems, mProcessItems); + // And add the process itself. + newItems.add(pi); + if (pi.mPid > 0) { + mProcessItems.add(pi); + } + // And finally the services running in it. + boolean needDivider = false; + for (ServiceItem si : pi.mServices.values()) { + si.mNeedDivider = needDivider; + needDivider = true; + newItems.add(si); + } + } + synchronized (mLock) { + mItems = newItems; + } } // Count number of interesting other (non-active) processes, and // build a list of all processes we will retrieve memory for. mAllProcessItems.clear(); mAllProcessItems.addAll(mProcessItems); - mNumBackgroundProcesses = 0; - mNumForegroundProcesses = 0; + int numBackgroundProcesses = 0; + int numForegroundProcesses = 0; + int numServiceProcesses = 0; NRP = mRunningProcesses.size(); for (int i=0; i<NRP; i++) { ProcessItem proc = mRunningProcesses.valueAt(i); @@ -584,19 +661,25 @@ public class RunningServices extends ListActivity // of our active ones, so add it up if needed. if (proc.mRunningProcessInfo.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { - mNumBackgroundProcesses++; + numBackgroundProcesses++; mAllProcessItems.add(proc); } else if (proc.mRunningProcessInfo.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) { - mNumForegroundProcesses++; + numForegroundProcesses++; mAllProcessItems.add(proc); + } else { + Log.i(TAG, "Unknown non-service process: " + + proc.mProcessName + " #" + proc.mPid); } + } else { + numServiceProcesses++; } } + long backgroundProcessMemory = 0; + long foregroundProcessMemory = 0; + long serviceProcessMemory = 0; try { - mBackgroundProcessMemory = 0; - mForegroundProcessMemory = 0; final int numProc = mAllProcessItems.size(); int[] pids = new int[numProc]; for (int i=0; i<numProc; i++) { @@ -608,21 +691,35 @@ public class RunningServices extends ListActivity ProcessItem proc = mAllProcessItems.get(i); changed |= proc.updateSize(context, mem[i], mSequence); if (proc.mCurSeq == mSequence) { - continue; - } - if (proc.mRunningProcessInfo.importance >= + serviceProcessMemory += proc.mSize; + } else if (proc.mRunningProcessInfo.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { - mBackgroundProcessMemory += proc.mSize; + backgroundProcessMemory += proc.mSize; } else if (proc.mRunningProcessInfo.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) { - mForegroundProcessMemory += proc.mSize; + foregroundProcessMemory += proc.mSize; } } } catch (RemoteException e) { } + synchronized (mLock) { + mNumBackgroundProcesses = numBackgroundProcesses; + mNumForegroundProcesses = numForegroundProcesses; + mNumServiceProcesses = numServiceProcesses; + mBackgroundProcessMemory = backgroundProcessMemory; + mForegroundProcessMemory = foregroundProcessMemory; + mServiceProcessMemory = serviceProcessMemory; + } + return changed; } + + ArrayList<BaseItem> getCurrentItems() { + synchronized (mLock) { + return mItems; + } + } } static class TimeTicker extends TextView { @@ -642,26 +739,38 @@ public class RunningServices extends ListActivity class ServiceListAdapter extends BaseAdapter { final State mState; final LayoutInflater mInflater; + ArrayList<BaseItem> mItems; ServiceListAdapter(State state) { mState = state; mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + refreshItems(); } + void refreshItems() { + ArrayList<BaseItem> newItems = mState.getCurrentItems(); + if (mItems != newItems) { + mItems = newItems; + } + if (mItems == null) { + mItems = new ArrayList<BaseItem>(); + } + } + public boolean hasStableIds() { return true; } public int getCount() { - return mState.mItems.size(); + return mItems.size(); } public Object getItem(int position) { - return mState.mItems.get(position); + return mItems.get(position); } public long getItemId(int position) { - return position; + return mItems.get(position).hashCode(); } public boolean areAllItemsEnabled() { @@ -669,7 +778,7 @@ public class RunningServices extends ListActivity } public boolean isEnabled(int position) { - return !mState.mItems.get(position).mIsProcess; + return !mItems.get(position).mIsProcess; } public View getView(int position, View convertView, ViewGroup parent) { @@ -696,35 +805,126 @@ public class RunningServices extends ListActivity } public void bindView(View view, int position) { - ViewHolder vh = (ViewHolder) view.getTag(); - BaseItem item = mState.mItems.get(position); - vh.name.setText(item.mDisplayLabel); - vh.separator.setVisibility(item.mNeedDivider - ? View.VISIBLE : View.INVISIBLE); - ActiveItem ai = new ActiveItem(); - ai.mRootView = view; - ai.mItem = item; - ai.mHolder = vh; - ai.mFirstRunTime = item.mActiveSince; - vh.description.setText(item.mDescription); - if (item.mIsProcess) { - view.setBackgroundColor(mProcessBgColor); - vh.icon.setImageDrawable(null); - vh.icon.setVisibility(View.GONE); - vh.description.setText(item.mDescription); - item.mCurSizeStr = null; - } else { - view.setBackgroundDrawable(null); - vh.icon.setImageDrawable(item.mPackageInfo.loadIcon(getPackageManager())); - vh.icon.setVisibility(View.VISIBLE); - vh.description.setText(item.mDescription); + synchronized (mState.mLock) { + ViewHolder vh = (ViewHolder) view.getTag(); + if (position >= mItems.size()) { + // List must have changed since we last reported its + // size... ignore here, we will be doing a data changed + // to refresh the entire list. + return; + } + BaseItem item = mItems.get(position); + vh.name.setText(item.mDisplayLabel); + vh.separator.setVisibility(item.mNeedDivider + ? View.VISIBLE : View.INVISIBLE); + ActiveItem ai = new ActiveItem(); + ai.mRootView = view; + ai.mItem = item; + ai.mHolder = vh; ai.mFirstRunTime = item.mActiveSince; + vh.description.setText(item.mDescription); + if (item.mIsProcess) { + view.setBackgroundColor(mProcessBgColor); + vh.icon.setImageDrawable(null); + vh.icon.setVisibility(View.GONE); + vh.description.setText(item.mDescription); + item.mCurSizeStr = null; + } else { + view.setBackgroundDrawable(null); + vh.icon.setImageDrawable(item.mPackageInfo.loadIcon(getPackageManager())); + vh.icon.setVisibility(View.VISIBLE); + vh.description.setText(item.mDescription); + ai.mFirstRunTime = item.mActiveSince; + } + ai.updateTime(RunningServices.this); + mActiveItems.put(view, ai); + } + } + } + + public static class LinearColorBar extends LinearLayout { + private float mRedRatio; + private float mYellowRatio; + private float mGreenRatio; + + final Rect mRect = new Rect(); + final Paint mPaint = new Paint(); + + public LinearColorBar(Context context, AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(false); + mPaint.setStyle(Paint.Style.FILL); + } + + public void setRatios(float red, float yellow, float green) { + mRedRatio = red; + mYellowRatio = yellow; + mGreenRatio = green; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int width = getWidth(); + mRect.top = 0; + mRect.bottom = getHeight(); + + int left = 0; + + int right = left + (int)(width*mRedRatio); + if (left < right) { + mRect.left = left; + mRect.right = right; + mPaint.setColor(0xffff8080); + canvas.drawRect(mRect, mPaint); + width -= (right-left); + left = right; + } + + right = left + (int)(width*mYellowRatio); + if (left < right) { + mRect.left = left; + mRect.right = right; + mPaint.setColor(0xffffff00); + canvas.drawRect(mRect, mPaint); + width -= (right-left); + left = right; + } + + right = left + width; + if (left < right) { + mRect.left = left; + mRect.right = right; + mPaint.setColor(0xff80ff80); + canvas.drawRect(mRect, mPaint); } - ai.updateTime(RunningServices.this); - mActiveItems.put(view, ai); } } + HandlerThread mBackgroundThread; + final class BackgroundHandler extends Handler { + public BackgroundHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_CONTENTS: + Message cmd = mHandler.obtainMessage(MSG_REFRESH_UI); + cmd.arg1 = mState.update(RunningServices.this, mAm) ? 1 : 0; + mHandler.sendMessage(cmd); + removeMessages(MSG_UPDATE_CONTENTS); + msg = obtainMessage(MSG_UPDATE_CONTENTS); + sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); + break; + } + } + }; + BackgroundHandler mBackgroundHandler; + final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -744,11 +944,8 @@ public class RunningServices extends ListActivity msg = obtainMessage(MSG_UPDATE_TIMES); sendMessageDelayed(msg, TIME_UPDATE_DELAY); break; - case MSG_UPDATE_CONTENTS: - updateList(); - removeMessages(MSG_UPDATE_CONTENTS); - msg = obtainMessage(MSG_UPDATE_CONTENTS); - sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); + case MSG_REFRESH_UI: + refreshUi(msg.arg1 != 0); break; } } @@ -823,6 +1020,7 @@ public class RunningServices extends ListActivity setContentView(R.layout.running_services); getListView().setDivider(null); getListView().setAdapter(new ServiceListAdapter(mState)); + mColorBar = (LinearColorBar)findViewById(R.id.color_bar); mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText); mForegroundProcessText = (TextView)findViewById(R.id.foregroundText); @@ -831,9 +1029,11 @@ public class RunningServices extends ListActivity Integer.valueOf(SystemProperties.get("ro.SECONDARY_SERVER_MEM"))*PAGE_SIZE; } - void updateList() { - if (mState.update(this, mAm)) { - ((ServiceListAdapter)(getListView().getAdapter())).notifyDataSetChanged(); + void refreshUi(boolean dataChanged) { + if (dataChanged) { + ServiceListAdapter adapter = (ServiceListAdapter)(getListView().getAdapter()); + adapter.refreshItems(); + adapter.notifyDataSetChanged(); } // This is the amount of available memory until we start killing @@ -843,26 +1043,36 @@ public class RunningServices extends ListActivity availMem = 0; } - if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses - || mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory - || mLastAvailMemory != availMem) { - mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses; - mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory; - mLastAvailMemory = availMem; - String availStr = availMem != 0 - ? Formatter.formatShortFileSize(this, availMem) : "0"; - String sizeStr = Formatter.formatShortFileSize(this, mLastBackgroundProcessMemory); - mBackgroundProcessText.setText(getResources().getString( - R.string.service_background_processes, - mLastNumBackgroundProcesses, availStr, sizeStr)); - } - if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses - || mLastForegroundProcessMemory != mState.mForegroundProcessMemory) { - mLastNumForegroundProcesses = mState.mNumForegroundProcesses; - mLastForegroundProcessMemory = mState.mForegroundProcessMemory; - String sizeStr = Formatter.formatShortFileSize(this, mLastForegroundProcessMemory); - mForegroundProcessText.setText(getResources().getString( - R.string.service_foreground_processes, mLastNumForegroundProcesses, sizeStr)); + synchronized (mState.mLock) { + if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses + || mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory + || mLastAvailMemory != availMem) { + mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses; + mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory; + mLastAvailMemory = availMem; + String availStr = availMem != 0 + ? Formatter.formatShortFileSize(this, availMem) : "0"; + String sizeStr = Formatter.formatShortFileSize(this, mLastBackgroundProcessMemory); + mBackgroundProcessText.setText(getResources().getString( + R.string.service_background_processes, + mLastNumBackgroundProcesses, availStr, sizeStr)); + } + if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses + || mLastForegroundProcessMemory != mState.mForegroundProcessMemory) { + mLastNumForegroundProcesses = mState.mNumForegroundProcesses; + mLastForegroundProcessMemory = mState.mForegroundProcessMemory; + String sizeStr = Formatter.formatShortFileSize(this, mLastForegroundProcessMemory); + mForegroundProcessText.setText(getResources().getString( + R.string.service_foreground_processes, mLastNumForegroundProcesses, sizeStr)); + } + mLastNumServiceProcesses = mState.mNumServiceProcesses; + mLastServiceProcessMemory = mState.mServiceProcessMemory; + + float totalMem = availMem + mLastBackgroundProcessMemory + + mLastForegroundProcessMemory + mLastServiceProcessMemory; + mColorBar.setRatios(mLastForegroundProcessMemory/totalMem, + mLastServiceProcessMemory/totalMem, + (availMem+mLastBackgroundProcessMemory)/totalMem); } } @@ -911,7 +1121,9 @@ public class RunningServices extends ListActivity if (mCurSelected != null) { stopService(new Intent().setComponent( ((ServiceItem)mCurSelected).mRunningService.service)); - updateList(); + if (mBackgroundHandler != null) { + mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS); + } } } @@ -919,19 +1131,26 @@ public class RunningServices extends ListActivity protected void onPause() { super.onPause(); mHandler.removeMessages(MSG_UPDATE_TIMES); - mHandler.removeMessages(MSG_UPDATE_CONTENTS); + if (mBackgroundThread != null) { + mBackgroundThread.quit(); + mBackgroundThread = null; + mBackgroundHandler = null; + } } @Override protected void onResume() { super.onResume(); - updateList(); + refreshUi(mState.update(this, mAm)); + mBackgroundThread = new HandlerThread("RunningServices"); + mBackgroundThread.start(); + mBackgroundHandler = new BackgroundHandler(mBackgroundThread.getLooper()); mHandler.removeMessages(MSG_UPDATE_TIMES); Message msg = mHandler.obtainMessage(MSG_UPDATE_TIMES); mHandler.sendMessageDelayed(msg, TIME_UPDATE_DELAY); - mHandler.removeMessages(MSG_UPDATE_CONTENTS); - msg = mHandler.obtainMessage(MSG_UPDATE_CONTENTS); - mHandler.sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); + mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS); + msg = mBackgroundHandler.obtainMessage(MSG_UPDATE_CONTENTS); + mBackgroundHandler.sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); } @Override diff --git a/src/com/android/settings/SdCardIntentReceiver.java b/src/com/android/settings/SdCardIntentReceiver.java deleted file mode 100644 index 9648ec1..0000000 --- a/src/com/android/settings/SdCardIntentReceiver.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2007 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.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.content.BroadcastReceiver; -import android.util.Config; -import android.util.Log; - -/** - * - */ -public class SdCardIntentReceiver extends BroadcastReceiver { - - private static final int SDCARD_STATUS = 1; - private static final String TAG = "SdCardIntentReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - NotificationManager nm = (NotificationManager) context - .getSystemService(Context.NOTIFICATION_SERVICE); - String action = intent.getAction(); - if (Config.LOGD) Log.d(TAG, "onReceiveIntent " + action); - - if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { - nm.cancel(SDCARD_STATUS); - - Intent statusIntent = new Intent(Intent.ACTION_MAIN, null); - statusIntent.setClass(context, SdCardSettings.class); - nm.notify(SDCARD_STATUS, new Notification(context, - android.R.drawable.stat_notify_sdcard, - null, - System.currentTimeMillis(), - context.getText(R.string.sdcard_setting), - null, - statusIntent)); - } else if (action.equals(Intent.ACTION_MEDIA_REMOVED)) { - nm.cancel(SDCARD_STATUS); - } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { - nm.cancel(SDCARD_STATUS); - - Intent statusIntent = new Intent(Intent.ACTION_MAIN, null); - statusIntent.setClass(context, SdCardSettings.class); - nm.notify(SDCARD_STATUS, new Notification(context, - android.R.drawable.stat_notify_sdcard_usb, - null, - System.currentTimeMillis(), - "SD Card", - null, - statusIntent)); - } - } -} diff --git a/src/com/android/settings/SdCardSettings.java b/src/com/android/settings/SdCardSettings.java deleted file mode 100644 index 67f3550..0000000 --- a/src/com/android/settings/SdCardSettings.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2007 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.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.os.RemoteException; -import android.os.Environment; -import android.os.IMountService; -import android.os.ServiceManager; -import android.os.StatFs; -import android.text.format.Formatter; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.TextView; - -import java.io.File; - - -public class SdCardSettings extends Activity -{ - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - setContentView(R.layout.sdcard_settings_screen); - - mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); - - mRemovedLayout = findViewById(R.id.removed); - mMountedLayout = findViewById(R.id.mounted); - mUnmountedLayout = findViewById(R.id.unmounted); - mScanningLayout = findViewById(R.id.scanning); - mSharedLayout = findViewById(R.id.shared); - mBadRemovalLayout = findViewById(R.id.bad_removal); - mReadOnlyStatus = findViewById(R.id.read_only); - - mMassStorage = (CheckBox)findViewById(R.id.mass_storage); - mMassStorage.setOnClickListener(mMassStorageListener); - - Button unmountButton = (Button)findViewById(R.id.sdcard_unmount); - unmountButton.setOnClickListener(mUnmountButtonHandler); - - Button formatButton = (Button)findViewById(R.id.sdcard_format); - formatButton.setOnClickListener(mFormatButtonHandler); - - mTotalSize = (TextView)findViewById(R.id.total); - mUsedSize = (TextView)findViewById(R.id.used); - mAvailableSize = (TextView)findViewById(R.id.available); - - // install an intent filter to receive SD card related events. - IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_REMOVED); - intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); - intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED); - intentFilter.addAction(Intent.ACTION_MEDIA_SHARED); - intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING); - intentFilter.addAction(Intent.ACTION_MEDIA_NOFS); - intentFilter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); - intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); - intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); - intentFilter.addDataScheme("file"); - registerReceiver(mReceiver, intentFilter); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - } - - @Override - public void onResume() { - super.onResume(); - update(); - } - - private void setLayout(View layout) { - mRemovedLayout.setVisibility(layout == mRemovedLayout ? View.VISIBLE : View.GONE); - mMountedLayout.setVisibility(layout == mMountedLayout ? View.VISIBLE : View.GONE); - mUnmountedLayout.setVisibility(layout == mUnmountedLayout ? View.VISIBLE : View.GONE); - mScanningLayout.setVisibility(layout == mScanningLayout ? View.VISIBLE : View.GONE); - mSharedLayout.setVisibility(layout == mSharedLayout ? View.VISIBLE : View.GONE); - mBadRemovalLayout.setVisibility(layout == mBadRemovalLayout ? View.VISIBLE : View.GONE); - } - - private void update() { - try { - mMassStorage.setChecked(mMountService.getMassStorageEnabled()); - } catch (RemoteException ex) { - } - - String status = Environment.getExternalStorageState(); - boolean readOnly = false; - - if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { - status = Environment.MEDIA_MOUNTED; - readOnly = true; - } - - if (status.equals(Environment.MEDIA_MOUNTED)) { - try { - File path = Environment.getExternalStorageDirectory(); - StatFs stat = new StatFs(path.getPath()); - long blockSize = stat.getBlockSize(); - long totalBlocks = stat.getBlockCount(); - long availableBlocks = stat.getAvailableBlocks(); - - mTotalSize.setText(formatSize(totalBlocks * blockSize)); - mUsedSize.setText(formatSize((totalBlocks - availableBlocks) * blockSize)); - mAvailableSize.setText(formatSize(availableBlocks * blockSize)); - } catch (IllegalArgumentException e) { - // this can occur if the SD card is removed, but we haven't received the - // ACTION_MEDIA_REMOVED Intent yet. - status = Environment.MEDIA_REMOVED; - } - - mReadOnlyStatus.setVisibility(readOnly ? View.VISIBLE : View.GONE); - setLayout(mMountedLayout); - } else if (status.equals(Environment.MEDIA_UNMOUNTED)) { - setLayout(mUnmountedLayout); - } else if (status.equals(Environment.MEDIA_REMOVED)) { - setLayout(mRemovedLayout); - } else if (status.equals(Environment.MEDIA_SHARED)) { - setLayout(mSharedLayout); - } else if (status.equals(Environment.MEDIA_BAD_REMOVAL)) { - setLayout(mBadRemovalLayout); - } - } - - private String formatSize(long size) { - return Formatter.formatFileSize(this, size); - } - - OnClickListener mMassStorageListener = new OnClickListener() { - public void onClick(View v) { - try { - mMountService.setMassStorageEnabled(mMassStorage.isChecked()); - } catch (RemoteException ex) { - } - } - }; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - update(); - } - }; - - OnClickListener mUnmountButtonHandler = new OnClickListener() { - public void onClick(View v) { - try { - mMountService.unmountMedia(Environment.getExternalStorageDirectory().toString()); - } catch (RemoteException ex) { - } - } - }; - - OnClickListener mFormatButtonHandler = new OnClickListener() { - public void onClick(View v) { - try { - mMountService.formatMedia(Environment.getExternalStorageDirectory().toString()); - } catch (RemoteException ex) { - } - } - }; - - private IMountService mMountService; - - private CheckBox mMassStorage; - - private TextView mTotalSize; - private TextView mUsedSize; - private TextView mAvailableSize; - - private View mRemovedLayout; - private View mMountedLayout; - private View mUnmountedLayout; - private View mScanningLayout; - private View mSharedLayout; - private View mBadRemovalLayout; - private View mReadOnlyStatus; -} diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java index fcacccc..1348d48 100644 --- a/src/com/android/settings/SecuritySettings.java +++ b/src/com/android/settings/SecuritySettings.java @@ -17,92 +17,81 @@ package com.android.settings; +import java.util.Observable; +import java.util.Observer; + import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.app.admin.DevicePolicyManager; import android.content.ContentQueryMap; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; import android.location.LocationManager; import android.os.Bundle; +import android.os.SystemProperties; import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; -import android.preference.PreferenceGroup; +import android.preference.PreferenceManager; import android.preference.PreferenceScreen; +import android.preference.Preference.OnPreferenceChangeListener; import android.provider.Settings; import android.security.Credentials; import android.security.KeyStore; -import android.text.Html; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; +import android.telephony.TelephonyManager; import android.util.Log; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.android.internal.widget.LockPatternUtils; -import android.telephony.TelephonyManager; - -import java.util.ArrayList; -import java.util.List; -import java.util.Observable; -import java.util.Observer; /** * Gesture lock pattern settings. */ public class SecuritySettings extends PreferenceActivity { + private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change"; // Lock Settings + private static final String PACKAGE = "com.android.settings"; + private static final String ICC_LOCK_SETTINGS = PACKAGE + ".IccLockSettings"; private static final String KEY_LOCK_ENABLED = "lockenabled"; private static final String KEY_VISIBLE_PATTERN = "visiblepattern"; - private static final String KEY_TACTILE_FEEDBACK_ENABLED = "tactilefeedback"; - private static final int CONFIRM_PATTERN_THEN_DISABLE_AND_CLEAR_REQUEST_CODE = 55; - - private static final String PREFS_NAME = "location_prefs"; - private static final String PREFS_USE_LOCATION = "use_location"; + private static final String KEY_TACTILE_FEEDBACK_ENABLED = "unlock_tactile_feedback"; - private LockPatternUtils mLockPatternUtils; - private CheckBoxPreference mLockEnabled; private CheckBoxPreference mVisiblePattern; private CheckBoxPreference mTactileFeedback; - private Preference mChoosePattern; private CheckBoxPreference mShowPassword; // Location Settings - private static final String LOCATION_CATEGORY = "location_category"; private static final String LOCATION_NETWORK = "location_network"; private static final String LOCATION_GPS = "location_gps"; private static final String ASSISTED_GPS = "assisted_gps"; + private static final int SET_OR_CHANGE_LOCK_METHOD_REQUEST = 123; // Credential storage - private static final int CSTOR_MIN_PASSWORD_LENGTH = 8; - - private static final int CSTOR_INIT_DIALOG = 1; - private static final int CSTOR_CHANGE_PASSWORD_DIALOG = 2; - private static final int CSTOR_UNLOCK_DIALOG = 3; - private static final int CSTOR_RESET_DIALOG = 4; - private CredentialStorage mCredentialStorage = new CredentialStorage(); private CheckBoxPreference mNetwork; private CheckBoxPreference mGps; private CheckBoxPreference mAssistedGps; + 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 final class SettingsObserver implements Observer { public void update(Observable o, Object arg) { updateToggles(); @@ -112,15 +101,14 @@ public class SecuritySettings extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.security_settings); - mLockPatternUtils = new LockPatternUtils(getContentResolver()); + mLockPatternUtils = new LockPatternUtils(this); - createPreferenceHierarchy(); + mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE); - mNetwork = (CheckBoxPreference) getPreferenceScreen().findPreference(LOCATION_NETWORK); - mGps = (CheckBoxPreference) getPreferenceScreen().findPreference(LOCATION_GPS); - mAssistedGps = (CheckBoxPreference) getPreferenceScreen().findPreference(ASSISTED_GPS); + mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this); + + createPreferenceHierarchy(); updateToggles(); @@ -131,45 +119,48 @@ public class SecuritySettings extends PreferenceActivity { null); mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null); mContentQueryMap.addObserver(new SettingsObserver()); - - mCredentialStorage.handleIntent(getIntent()); } private PreferenceScreen createPreferenceHierarchy() { - // Root PreferenceScreen root = this.getPreferenceScreen(); + if (root != null) { + root.removeAll(); + } + addPreferencesFromResource(R.xml.security_settings); + root = this.getPreferenceScreen(); + + mNetwork = (CheckBoxPreference) getPreferenceScreen().findPreference(LOCATION_NETWORK); + mGps = (CheckBoxPreference) getPreferenceScreen().findPreference(LOCATION_GPS); + mAssistedGps = (CheckBoxPreference) getPreferenceScreen().findPreference(ASSISTED_GPS); + + PreferenceManager pm = getPreferenceManager(); + + // Lock screen + if (!mLockPatternUtils.isSecure()) { + addPreferencesFromResource(R.xml.security_settings_chooser); + } else { + switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) { + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + addPreferencesFromResource(R.xml.security_settings_pattern); + break; + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + addPreferencesFromResource(R.xml.security_settings_pin); + break; + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + addPreferencesFromResource(R.xml.security_settings_password); + break; + } + } - // Inline preferences - PreferenceCategory inlinePrefCat = new PreferenceCategory(this); - inlinePrefCat.setTitle(R.string.lock_settings_title); - root.addPreference(inlinePrefCat); - - // change pattern lock - Intent intent = new Intent(); - intent.setClassName("com.android.settings", - "com.android.settings.ChooseLockPatternTutorial"); - mChoosePattern = getPreferenceManager().createPreferenceScreen(this); - mChoosePattern.setIntent(intent); - inlinePrefCat.addPreference(mChoosePattern); - - // autolock toggle - mLockEnabled = new LockEnabledPref(this); - mLockEnabled.setTitle(R.string.lockpattern_settings_enable_title); - mLockEnabled.setSummary(R.string.lockpattern_settings_enable_summary); - mLockEnabled.setKey(KEY_LOCK_ENABLED); - inlinePrefCat.addPreference(mLockEnabled); + // set or change current. Should be common to all unlock preference screens + // mSetOrChange = (PreferenceScreen) pm.findPreference(KEY_UNLOCK_SET_OR_CHANGE); // visible pattern - mVisiblePattern = new CheckBoxPreference(this); - mVisiblePattern.setKey(KEY_VISIBLE_PATTERN); - mVisiblePattern.setTitle(R.string.lockpattern_settings_enable_visible_pattern_title); - inlinePrefCat.addPreference(mVisiblePattern); + mVisiblePattern = (CheckBoxPreference) pm.findPreference(KEY_VISIBLE_PATTERN); - // tactile feedback - mTactileFeedback = new CheckBoxPreference(this); - mTactileFeedback.setKey(KEY_TACTILE_FEEDBACK_ENABLED); - mTactileFeedback.setTitle(R.string.lockpattern_settings_enable_tactile_feedback_title); - inlinePrefCat.addPreference(mTactileFeedback); + // tactile feedback. Should be common to all unlock preference screens. + mTactileFeedback = (CheckBoxPreference) pm.findPreference(KEY_TACTILE_FEEDBACK_ENABLED); int activePhoneType = TelephonyManager.getDefault().getPhoneType(); @@ -180,10 +171,7 @@ public class SecuritySettings extends PreferenceActivity { .createPreferenceScreen(this); simLockPreferences.setTitle(R.string.sim_lock_settings_category); // Intent to launch SIM lock settings - intent = new Intent(); - intent.setClassName("com.android.settings", "com.android.settings.IccLockSettings"); - simLockPreferences.setIntent(intent); - + simLockPreferences.setIntent(new Intent().setClassName(PACKAGE, ICC_LOCK_SETTINGS)); PreferenceCategory simLockCat = new PreferenceCategory(this); simLockCat.setTitle(R.string.sim_lock_settings_title); root.addPreference(simLockCat); @@ -202,11 +190,24 @@ public class SecuritySettings extends PreferenceActivity { showPassword.setPersistent(false); passwordsCat.addPreference(showPassword); + // Device policies + PreferenceCategory devicePoliciesCat = new PreferenceCategory(this); + devicePoliciesCat.setTitle(R.string.device_admin_title); + root.addPreference(devicePoliciesCat); + + Preference deviceAdminButton = new Preference(this); + deviceAdminButton.setTitle(R.string.manage_device_admin); + deviceAdminButton.setSummary(R.string.manage_device_admin_summary); + Intent deviceAdminIntent = new Intent(); + deviceAdminIntent.setClass(this, DeviceAdminSettings.class); + deviceAdminButton.setIntent(deviceAdminIntent); + devicePoliciesCat.addPreference(deviceAdminButton); + // Credential storage PreferenceCategory credentialsCat = new PreferenceCategory(this); credentialsCat.setTitle(R.string.credentials_category); root.addPreference(credentialsCat); - mCredentialStorage.createPreferences(credentialsCat); + mCredentialStorage.createPreferences(credentialsCat, CredentialStorage.TYPE_KEYSTORE); return root; } @@ -215,19 +216,13 @@ public class SecuritySettings extends PreferenceActivity { protected void onResume() { super.onResume(); - boolean patternExists = mLockPatternUtils.savedPatternExists(); - mLockEnabled.setEnabled(patternExists); - mVisiblePattern.setEnabled(patternExists); - mTactileFeedback.setEnabled(patternExists); - - mLockEnabled.setChecked(mLockPatternUtils.isLockPatternEnabled()); - mVisiblePattern.setChecked(mLockPatternUtils.isVisiblePatternEnabled()); - mTactileFeedback.setChecked(mLockPatternUtils.isTactileFeedbackEnabled()); - - int chooseStringRes = mLockPatternUtils.savedPatternExists() ? - R.string.lockpattern_settings_change_lock_pattern : - R.string.lockpattern_settings_choose_lock_pattern; - mChoosePattern.setTitle(chooseStringRes); + final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); + if (mVisiblePattern != null) { + mVisiblePattern.setChecked(lockPatternUtils.isVisiblePatternEnabled()); + } + if (mTactileFeedback != null) { + mTactileFeedback.setChecked(lockPatternUtils.isTactileFeedbackEnabled()); + } mShowPassword.setChecked(Settings.System.getInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) != 0); @@ -240,12 +235,16 @@ public class SecuritySettings extends PreferenceActivity { Preference preference) { final String key = preference.getKey(); - if (KEY_LOCK_ENABLED.equals(key)) { - mLockPatternUtils.setLockPatternEnabled(isToggled(preference)); + final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); + if (KEY_UNLOCK_SET_OR_CHANGE.equals(key)) { + Intent intent = new Intent(this, ChooseLockGeneric.class); + startActivityForResult(intent, SET_OR_CHANGE_LOCK_METHOD_REQUEST); + } else if (KEY_LOCK_ENABLED.equals(key)) { + lockPatternUtils.setLockPatternEnabled(isToggled(preference)); } else if (KEY_VISIBLE_PATTERN.equals(key)) { - mLockPatternUtils.setVisiblePatternEnabled(isToggled(preference)); + lockPatternUtils.setVisiblePatternEnabled(isToggled(preference)); } else if (KEY_TACTILE_FEEDBACK_ENABLED.equals(key)) { - mLockPatternUtils.setTactileFeedbackEnabled(isToggled(preference)); + lockPatternUtils.setTactileFeedbackEnabled(isToggled(preference)); } else if (preference == mShowPassword) { Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, mShowPassword.isChecked() ? 1 : 0); @@ -267,11 +266,6 @@ public class SecuritySettings extends PreferenceActivity { return false; } - private void showPrivacyPolicy() { - Intent intent = new Intent("android.settings.TERMS"); - startActivity(intent); - } - /* * Creates toggles for each available location provider */ @@ -294,102 +288,56 @@ public class SecuritySettings extends PreferenceActivity { } /** - * For the user to disable keyguard, we first make them verify their - * existing pattern. - */ - private class LockEnabledPref extends CheckBoxPreference { - - public LockEnabledPref(Context context) { - super(context); - } - - @Override - protected void onClick() { - if (mLockPatternUtils.savedPatternExists() && isChecked()) { - confirmPatternThenDisableAndClear(); - } else { - super.onClick(); - } - } - } - - /** - * Launch screen to confirm the existing lock pattern. - * @see #onActivityResult(int, int, android.content.Intent) - */ - private void confirmPatternThenDisableAndClear() { - final Intent intent = new Intent(); - intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern"); - startActivityForResult(intent, CONFIRM_PATTERN_THEN_DISABLE_AND_CLEAR_REQUEST_CODE); - } - - /** * @see #confirmPatternThenDisableAndClear */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - - final boolean resultOk = resultCode == Activity.RESULT_OK; - - if ((requestCode == CONFIRM_PATTERN_THEN_DISABLE_AND_CLEAR_REQUEST_CODE) - && resultOk) { - mLockPatternUtils.setLockPatternEnabled(false); - mLockPatternUtils.saveLockPattern(null); - } - } - - @Override - protected Dialog onCreateDialog(int id) { - return mCredentialStorage.createDialog(id); + createPreferenceHierarchy(); } private class CredentialStorage implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener, Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { private static final int MINIMUM_PASSWORD_LENGTH = 8; - private static final int UNLOCK_DIALOG = 1; - private static final int PASSWORD_DIALOG = 2; - private static final int RESET_DIALOG = 3; + + private static final int TYPE_KEYSTORE = 0; + + // Dialog identifiers + private static final int DLG_BASE = 0; + private static final int DLG_UNLOCK = DLG_BASE + 1; + private static final int DLG_PASSWORD = DLG_UNLOCK + 1; + private static final int DLG_RESET = DLG_PASSWORD + 1; private KeyStore mKeyStore = KeyStore.getInstance(); - private int mState = mKeyStore.test(); - private int mDialogId; - private boolean mSubmit; + private int mState; + private boolean mSubmit = false; + private boolean mExternal = false; + + private int mShowingDialog = 0; + // Key Store controls private CheckBoxPreference mAccessCheckBox; private Preference mInstallButton; private Preference mPasswordButton; private Preference mResetButton; - private Intent mExternalIntent; - - void handleIntent(Intent intent) { - if (intent != null) { - if (Credentials.UNLOCK_ACTION.equals(intent.getAction())) { - mExternalIntent = intent; - showDialog((mState == KeyStore.UNINITIALIZED) ? - PASSWORD_DIALOG : UNLOCK_DIALOG); - } - } - } - void resume() { mState = mKeyStore.test(); updatePreferences(mState); - } - Dialog createDialog(int id) { - mDialogId = id; - switch (id) { - case UNLOCK_DIALOG: - return createUnlockDialog(); - case PASSWORD_DIALOG: - return createPasswordDialog(); - case RESET_DIALOG: - return createResetDialog(); + Intent intent = getIntent(); + if (!mExternal && intent != null && + Credentials.UNLOCK_ACTION.equals(intent.getAction())) { + mExternal = true; + if (mState == KeyStore.UNINITIALIZED) { + showPasswordDialog(); + } else if (mState == KeyStore.LOCKED) { + showUnlockDialog(); + } else { + finish(); + } } - return null; } private void initialize(String password) { @@ -420,23 +368,22 @@ public class SecuritySettings extends PreferenceActivity { public boolean onPreferenceChange(Preference preference, Object value) { if (preference == mAccessCheckBox) { if ((Boolean) value) { - showDialog((mState == KeyStore.UNINITIALIZED) ? - PASSWORD_DIALOG : UNLOCK_DIALOG); + showUnlockDialog(); } else { lock(); } return true; } - return false; + return true; } public boolean onPreferenceClick(Preference preference) { if (preference == mInstallButton) { Credentials.getInstance().installFromSdCard(SecuritySettings.this); } else if (preference == mPasswordButton) { - showDialog(PASSWORD_DIALOG); + showPasswordDialog(); } else if (preference == mResetButton) { - showDialog(RESET_DIALOG); + showResetDialog(); } else { return false; } @@ -454,14 +401,12 @@ public class SecuritySettings extends PreferenceActivity { if (mSubmit && !isFinishing()) { mSubmit = false; if (!checkPassword((Dialog) dialog)) { - showDialog(mDialogId); + ((Dialog) dialog).show(); return; } } - removeDialog(mDialogId); updatePreferences(mState); - if (mExternalIntent != null) { - mExternalIntent = null; + if (mExternal) { finish(); } } @@ -534,36 +479,44 @@ public class SecuritySettings extends PreferenceActivity { } } - private void createPreferences(PreferenceCategory category) { - mAccessCheckBox = new CheckBoxPreference(SecuritySettings.this); - mAccessCheckBox.setTitle(R.string.credentials_access); - mAccessCheckBox.setSummary(R.string.credentials_access_summary); - mAccessCheckBox.setOnPreferenceChangeListener(this); - category.addPreference(mAccessCheckBox); - - mInstallButton = new Preference(SecuritySettings.this); - mInstallButton.setTitle(R.string.credentials_install_certificates); - mInstallButton.setSummary(R.string.credentials_install_certificates_summary); - mInstallButton.setOnPreferenceClickListener(this); - category.addPreference(mInstallButton); - - mPasswordButton = new Preference(SecuritySettings.this); - mPasswordButton.setTitle(R.string.credentials_set_password); - mPasswordButton.setSummary(R.string.credentials_set_password_summary); - mPasswordButton.setOnPreferenceClickListener(this); - category.addPreference(mPasswordButton); - - mResetButton = new Preference(SecuritySettings.this); - mResetButton.setTitle(R.string.credentials_reset); - mResetButton.setSummary(R.string.credentials_reset_summary); - mResetButton.setOnPreferenceClickListener(this); - category.addPreference(mResetButton); + private void createPreferences(PreferenceCategory category, int type) { + switch(type) { + case TYPE_KEYSTORE: + mAccessCheckBox = new CheckBoxPreference(SecuritySettings.this); + mAccessCheckBox.setTitle(R.string.credentials_access); + mAccessCheckBox.setSummary(R.string.credentials_access_summary); + mAccessCheckBox.setOnPreferenceChangeListener(this); + category.addPreference(mAccessCheckBox); + + mInstallButton = new Preference(SecuritySettings.this); + mInstallButton.setTitle(R.string.credentials_install_certificates); + mInstallButton.setSummary(R.string.credentials_install_certificates_summary); + mInstallButton.setOnPreferenceClickListener(this); + category.addPreference(mInstallButton); + + mPasswordButton = new Preference(SecuritySettings.this); + mPasswordButton.setTitle(R.string.credentials_set_password); + mPasswordButton.setSummary(R.string.credentials_set_password_summary); + mPasswordButton.setOnPreferenceClickListener(this); + category.addPreference(mPasswordButton); + + mResetButton = new Preference(SecuritySettings.this); + mResetButton.setTitle(R.string.credentials_reset); + mResetButton.setSummary(R.string.credentials_reset_summary); + mResetButton.setOnPreferenceClickListener(this); + category.addPreference(mResetButton); + break; + + } } private void updatePreferences(int state) { - mAccessCheckBox.setEnabled(state != KeyStore.UNINITIALIZED); mAccessCheckBox.setChecked(state == KeyStore.NO_ERROR); + mResetButton.setEnabled(state != KeyStore.UNINITIALIZED); + mAccessCheckBox.setEnabled(state != KeyStore.UNINITIALIZED); + + // Encrypted File system preferences // Show a toast message if the state is changed. if (mState == state) { @@ -581,12 +534,12 @@ public class SecuritySettings extends PreferenceActivity { mState = state; } - private Dialog createUnlockDialog() { + private void showUnlockDialog() { View view = View.inflate(SecuritySettings.this, R.layout.credentials_unlock_dialog, null); - // show extra hint only when the action comes from outside - if (mExternalIntent != null) { + // Show extra hint only when the action comes from outside. + if (mExternal) { view.findViewById(R.id.hint).setVisibility(View.VISIBLE); } @@ -597,10 +550,11 @@ public class SecuritySettings extends PreferenceActivity { .setNegativeButton(android.R.string.cancel, this) .create(); dialog.setOnDismissListener(this); - return dialog; + mShowingDialog = DLG_UNLOCK; + dialog.show(); } - private Dialog createPasswordDialog() { + private void showPasswordDialog() { View view = View.inflate(SecuritySettings.this, R.layout.credentials_password_dialog, null); @@ -618,17 +572,19 @@ public class SecuritySettings extends PreferenceActivity { .setNegativeButton(android.R.string.cancel, this) .create(); dialog.setOnDismissListener(this); - return dialog; + mShowingDialog = DLG_PASSWORD; + dialog.show(); } - private Dialog createResetDialog() { - return new AlertDialog.Builder(SecuritySettings.this) + private void showResetDialog() { + mShowingDialog = DLG_RESET; + new AlertDialog.Builder(SecuritySettings.this) .setTitle(android.R.string.dialog_alert_title) .setIcon(android.R.drawable.ic_dialog_alert) .setMessage(R.string.credentials_reset_hint) .setNeutralButton(getString(android.R.string.ok), this) .setNegativeButton(getString(android.R.string.cancel), this) - .create(); + .create().show(); } } } diff --git a/src/com/android/settings/SettingsSafetyLegalActivity.java b/src/com/android/settings/SettingsSafetyLegalActivity.java index 619dc94..0c51928 100644 --- a/src/com/android/settings/SettingsSafetyLegalActivity.java +++ b/src/com/android/settings/SettingsSafetyLegalActivity.java @@ -58,7 +58,11 @@ public class SettingsSafetyLegalActivity extends AlertActivity // Begin accessing mWebView.getSettings().setJavaScriptEnabled(true); - mWebView.loadUrl(userSafetylegalUrl); + if (savedInstanceState == null) { + mWebView.loadUrl(userSafetylegalUrl); + } else { + mWebView.restoreState(savedInstanceState); + } mWebView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { @@ -110,4 +114,10 @@ public class SettingsSafetyLegalActivity extends AlertActivity public void onCancel(DialogInterface dialog) { finish(); } + + @Override + public void onSaveInstanceState(Bundle icicle) { + mWebView.saveState(icicle); + super.onSaveInstanceState(icicle); + } } diff --git a/src/com/android/settings/SoundAndDisplaySettings.java b/src/com/android/settings/SoundSettings.java index 29eb878..bfb5566 100644 --- a/src/com/android/settings/SoundAndDisplaySettings.java +++ b/src/com/android/settings/SoundSettings.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.os.Bundle; -import android.os.IMountService; import android.os.RemoteException; import android.os.ServiceManager; import android.preference.CheckBoxPreference; @@ -40,7 +39,7 @@ import android.telephony.TelephonyManager; import android.util.Log; import android.view.IWindowManager; -public class SoundAndDisplaySettings extends PreferenceActivity implements +public class SoundSettings extends PreferenceActivity implements Preference.OnPreferenceChangeListener { private static final String TAG = "SoundAndDisplaysSettings"; @@ -50,23 +49,20 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements private static final String KEY_SILENT = "silent"; private static final String KEY_VIBRATE = "vibrate"; - private static final String KEY_SCREEN_TIMEOUT = "screen_timeout"; private static final String KEY_DTMF_TONE = "dtmf_tone"; private static final String KEY_SOUND_EFFECTS = "sound_effects"; private static final String KEY_HAPTIC_FEEDBACK = "haptic_feedback"; - private static final String KEY_ANIMATIONS = "animations"; - private static final String KEY_ACCELEROMETER = "accelerometer"; - private static final String KEY_PLAY_MEDIA_NOTIFICATION_SOUNDS = - "play_media_notification_sounds"; private static final String KEY_EMERGENCY_TONE = "emergency_tone"; private static final String KEY_SOUND_SETTINGS = "sound_settings"; private static final String KEY_NOTIFICATION_PULSE = "notification_pulse"; + private static final String KEY_LOCK_SOUNDS = "lock_sounds"; - private CheckBoxPreference mSilent; - - private CheckBoxPreference mPlayMediaNotificationSounds; + private static final String VALUE_VIBRATE_NEVER = "never"; + private static final String VALUE_VIBRATE_ALWAYS = "always"; + private static final String VALUE_VIBRATE_ONLY_SILENT = "silent"; + private static final String VALUE_VIBRATE_UNLESS_SILENT = "notsilent"; - private IMountService mMountService = null; + private CheckBoxPreference mSilent; /* * If we are currently in one of the silent modes (the ringer mode is set to either @@ -75,19 +71,15 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements * Otherwise, it will adjust the normal ringer mode's ring or ring+vibrate * setting. */ - private CheckBoxPreference mVibrate; + private ListPreference mVibrate; private CheckBoxPreference mDtmfTone; private CheckBoxPreference mSoundEffects; private CheckBoxPreference mHapticFeedback; - private ListPreference mAnimations; - private CheckBoxPreference mAccelerometer; private CheckBoxPreference mNotificationPulse; - private float[] mAnimationScales; + private CheckBoxPreference mLockSounds; private AudioManager mAudioManager; - private IWindowManager mWindowManager; - private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -106,11 +98,8 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements int activePhoneType = TelephonyManager.getDefault().getPhoneType(); mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); - mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); - - addPreferencesFromResource(R.xml.sound_and_display_settings); + addPreferencesFromResource(R.xml.sound_settings); if (TelephonyManager.PHONE_TYPE_CDMA != activePhoneType) { // device is not CDMA, do not display CDMA emergency_tone @@ -118,9 +107,10 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements } mSilent = (CheckBoxPreference) findPreference(KEY_SILENT); - mPlayMediaNotificationSounds = (CheckBoxPreference) findPreference(KEY_PLAY_MEDIA_NOTIFICATION_SOUNDS); - mVibrate = (CheckBoxPreference) findPreference(KEY_VIBRATE); + mVibrate = (ListPreference) findPreference(KEY_VIBRATE); + mVibrate.setOnPreferenceChangeListener(this); + mDtmfTone = (CheckBoxPreference) findPreference(KEY_DTMF_TONE); mDtmfTone.setPersistent(false); mDtmfTone.setChecked(Settings.System.getInt(resolver, @@ -133,16 +123,10 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements mHapticFeedback.setPersistent(false); mHapticFeedback.setChecked(Settings.System.getInt(resolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0); - mAnimations = (ListPreference) findPreference(KEY_ANIMATIONS); - mAnimations.setOnPreferenceChangeListener(this); - mAccelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER); - mAccelerometer.setPersistent(false); - - ListPreference screenTimeoutPreference = - (ListPreference) findPreference(KEY_SCREEN_TIMEOUT); - screenTimeoutPreference.setValue(String.valueOf(Settings.System.getInt( - resolver, SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE))); - screenTimeoutPreference.setOnPreferenceChangeListener(this); + mLockSounds = (CheckBoxPreference) findPreference(KEY_LOCK_SOUNDS); + mLockSounds.setPersistent(false); + mLockSounds.setChecked(Settings.System.getInt(resolver, + Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) != 0); if (TelephonyManager.PHONE_TYPE_CDMA == activePhoneType) { ListPreference emergencyTonePreference = @@ -187,8 +171,85 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements unregisterReceiver(mReceiver); } + private String getPhoneVibrateSettingValue() { + boolean vibeInSilent = (Settings.System.getInt( + getContentResolver(), + Settings.System.VIBRATE_IN_SILENT, + 1) == 1); + + // Control phone vibe independent of silent mode + int callsVibrateSetting = + mAudioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); + + if (vibeInSilent) { + if (callsVibrateSetting == AudioManager.VIBRATE_SETTING_OFF) { + // this state does not make sense; fix it up for the user + mAudioManager.setVibrateSetting( + AudioManager.VIBRATE_TYPE_RINGER, + AudioManager.VIBRATE_SETTING_ONLY_SILENT); + } + if (callsVibrateSetting == AudioManager.VIBRATE_SETTING_ON) { + return VALUE_VIBRATE_ALWAYS; + } else { + return VALUE_VIBRATE_ONLY_SILENT; + } + } else { + if (callsVibrateSetting == AudioManager.VIBRATE_SETTING_ONLY_SILENT) { + // this state does not make sense; fix it up + mAudioManager.setVibrateSetting( + AudioManager.VIBRATE_TYPE_RINGER, + AudioManager.VIBRATE_SETTING_OFF); + } + if (callsVibrateSetting == AudioManager.VIBRATE_SETTING_ON) { + return VALUE_VIBRATE_UNLESS_SILENT; + } else { + return VALUE_VIBRATE_NEVER; + } + } + } + + private void setPhoneVibrateSettingValue(String value) { + boolean vibeInSilent; + int callsVibrateSetting; + + if (value.equals(VALUE_VIBRATE_UNLESS_SILENT)) { + callsVibrateSetting = AudioManager.VIBRATE_SETTING_ON; + vibeInSilent = false; + } else if (value.equals(VALUE_VIBRATE_NEVER)) { + callsVibrateSetting = AudioManager.VIBRATE_SETTING_OFF; + vibeInSilent = false; + } else if (value.equals(VALUE_VIBRATE_ONLY_SILENT)) { + callsVibrateSetting = AudioManager.VIBRATE_SETTING_ONLY_SILENT; + vibeInSilent = true; + } else { //VALUE_VIBRATE_ALWAYS + callsVibrateSetting = AudioManager.VIBRATE_SETTING_ON; + vibeInSilent = true; + } + + Settings.System.putInt(getContentResolver(), + Settings.System.VIBRATE_IN_SILENT, + vibeInSilent ? 1 : 0); + + // might need to switch the ringer mode from one kind of "silent" to + // another + if (mSilent.isChecked()) { + mAudioManager.setRingerMode( + vibeInSilent ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_SILENT); + } + + mAudioManager.setVibrateSetting( + AudioManager.VIBRATE_TYPE_RINGER, + callsVibrateSetting); + } + + // updateState in fact updates the UI to reflect the system state private void updateState(boolean force) { final int ringerMode = mAudioManager.getRingerMode(); + + // NB: in the UI we now simply call this "silent mode". A separate + // setting controls whether we're in RINGER_MODE_SILENT or + // RINGER_MODE_VIBRATE. final boolean silentOrVibrateMode = ringerMode != AudioManager.RINGER_MODE_NORMAL; @@ -196,21 +257,12 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements mSilent.setChecked(silentOrVibrateMode); } - try { - mPlayMediaNotificationSounds.setChecked(mMountService.getPlayNotificationSounds()); - } catch (RemoteException e) { - } + String phoneVibrateSetting = getPhoneVibrateSettingValue(); - boolean vibrateSetting; - if (silentOrVibrateMode) { - vibrateSetting = ringerMode == AudioManager.RINGER_MODE_VIBRATE; - } else { - vibrateSetting = mAudioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER) - == AudioManager.VIBRATE_SETTING_ON; - } - if (vibrateSetting != mVibrate.isChecked() || force) { - mVibrate.setChecked(vibrateSetting); + if (! phoneVibrateSetting.equals(mVibrate.getValue()) || force) { + mVibrate.setValue(phoneVibrateSetting); } + mVibrate.setSummary(mVibrate.getEntry()); int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); @@ -218,72 +270,23 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements mSilent.setSummary(isAlarmInclSilentMode ? R.string.silent_mode_incl_alarm_summary : R.string.silent_mode_summary); - - int animations = 0; - try { - mAnimationScales = mWindowManager.getAnimationScales(); - } catch (RemoteException e) { - } - if (mAnimationScales != null) { - if (mAnimationScales.length >= 1) { - animations = ((int)(mAnimationScales[0]+.5f)) % 10; - } - if (mAnimationScales.length >= 2) { - animations += (((int)(mAnimationScales[1]+.5f)) & 0x7) * 10; - } - } - int idx = 0; - int best = 0; - CharSequence[] aents = mAnimations.getEntryValues(); - for (int i=0; i<aents.length; i++) { - int val = Integer.parseInt(aents[i].toString()); - if (val <= animations && val > best) { - best = val; - idx = i; - } - } - mAnimations.setValueIndex(idx); - updateAnimationsSummary(mAnimations.getValue()); - mAccelerometer.setChecked(Settings.System.getInt( - getContentResolver(), - Settings.System.ACCELEROMETER_ROTATION, 0) != 0); - } - - private void updateAnimationsSummary(Object value) { - CharSequence[] summaries = getResources().getTextArray(R.array.animations_summaries); - CharSequence[] values = mAnimations.getEntryValues(); - for (int i=0; i<values.length; i++) { - //Log.i("foo", "Comparing entry "+ values[i] + " to current " - // + mAnimations.getValue()); - if (values[i].equals(value)) { - mAnimations.setSummary(summaries[i]); - break; - } - } - } - - private void setRingerMode(boolean silent, boolean vibrate) { - if (silent) { - mAudioManager.setRingerMode(vibrate ? AudioManager.RINGER_MODE_VIBRATE : - AudioManager.RINGER_MODE_SILENT); - } else { - mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); - mAudioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, - vibrate ? AudioManager.VIBRATE_SETTING_ON - : AudioManager.VIBRATE_SETTING_OFF); - } } @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - if (preference == mSilent || preference == mVibrate) { - setRingerMode(mSilent.isChecked(), mVibrate.isChecked()); - if (preference == mSilent) updateState(false); - } else if (preference == mPlayMediaNotificationSounds) { - try { - mMountService.setPlayNotificationSounds(mPlayMediaNotificationSounds.isChecked()); - } catch (RemoteException e) { + if (preference == mSilent) { + if (mSilent.isChecked()) { + boolean vibeInSilent = (1 == Settings.System.getInt( + getContentResolver(), + Settings.System.VIBRATE_IN_SILENT, + 1)); + mAudioManager.setRingerMode( + vibeInSilent ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); } + updateState(false); } else if (preference == mDtmfTone) { Settings.System.putInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, mDtmfTone.isChecked() ? 1 : 0); @@ -301,10 +304,10 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements Settings.System.putInt(getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, mHapticFeedback.isChecked() ? 1 : 0); - } else if (preference == mAccelerometer) { - Settings.System.putInt(getContentResolver(), - Settings.System.ACCELEROMETER_ROTATION, - mAccelerometer.isChecked() ? 1 : 0); + } else if (preference == mLockSounds) { + Settings.System.putInt(getContentResolver(), Settings.System.LOCKSCREEN_SOUNDS_ENABLED, + mLockSounds.isChecked() ? 1 : 0); + } else if (preference == mNotificationPulse) { boolean value = mNotificationPulse.isChecked(); Settings.System.putInt(getContentResolver(), @@ -316,34 +319,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements public boolean onPreferenceChange(Preference preference, Object objValue) { final String key = preference.getKey(); - if (KEY_ANIMATIONS.equals(key)) { - try { - int value = Integer.parseInt((String) objValue); - if (mAnimationScales.length >= 1) { - mAnimationScales[0] = value%10; - } - if (mAnimationScales.length >= 2) { - mAnimationScales[1] = (value/10)%10; - } - try { - mWindowManager.setAnimationScales(mAnimationScales); - } catch (RemoteException e) { - } - updateAnimationsSummary(objValue); - } catch (NumberFormatException e) { - Log.e(TAG, "could not persist animation setting", e); - } - - } - if (KEY_SCREEN_TIMEOUT.equals(key)) { - int value = Integer.parseInt((String) objValue); - try { - Settings.System.putInt(getContentResolver(), - SCREEN_OFF_TIMEOUT, value); - } catch (NumberFormatException e) { - Log.e(TAG, "could not persist screen timeout setting", e); - } - } else if (KEY_EMERGENCY_TONE.equals(key)) { + if (KEY_EMERGENCY_TONE.equals(key)) { int value = Integer.parseInt((String) objValue); try { Settings.System.putInt(getContentResolver(), @@ -351,6 +327,9 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements } catch (NumberFormatException e) { Log.e(TAG, "could not persist emergency tone setting", e); } + } else if (preference == mVibrate) { + setPhoneVibrateSettingValue(objValue.toString()); + updateState(false); } return true; diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java new file mode 100644 index 0000000..9eee4e0 --- /dev/null +++ b/src/com/android/settings/TetherSettings.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2008 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 com.android.settings.wifi.WifiApEnabler; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.os.SystemProperties; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.AssetManager; +import android.net.ConnectivityManager; +import android.os.Environment; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.provider.Settings; +import android.util.Log; +import android.webkit.WebView; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Locale; + +/* + * Displays preferences for Tethering. + */ +public class TetherSettings extends PreferenceActivity { + private static final String USB_TETHER_SETTINGS = "usb_tether_settings"; + private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; + private static final String WIFI_AP_SETTINGS = "wifi_ap_settings"; + private static final String TETHERING_HELP = "tethering_help"; + private static final String USB_HELP_MODIFIER = "usb_"; + private static final String WIFI_HELP_MODIFIER = "wifi_"; + private static final String HELP_URL = "file:///android_asset/html/%y%z/tethering_%xhelp.html"; + private static final String HELP_PATH = "html/%y%z/tethering_help.html"; + + private static final int DIALOG_TETHER_HELP = 1; + + private WebView mView; + private CheckBoxPreference mUsbTether; + + private CheckBoxPreference mEnableWifiAp; + private PreferenceScreen mWifiApSettings; + private WifiApEnabler mWifiApEnabler; + private PreferenceScreen mTetherHelp; + + private BroadcastReceiver mTetherChangeReceiver; + + private String[] mUsbRegexs; + private ArrayList mUsbIfaces; + + private String[] mWifiRegexs; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.tether_prefs); + + mEnableWifiAp = (CheckBoxPreference) findPreference(ENABLE_WIFI_AP); + mWifiApSettings = (PreferenceScreen) findPreference(WIFI_AP_SETTINGS); + mUsbTether = (CheckBoxPreference) findPreference(USB_TETHER_SETTINGS); + mTetherHelp = (PreferenceScreen) findPreference(TETHERING_HELP); + + ConnectivityManager cm = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + + mUsbRegexs = cm.getTetherableUsbRegexs(); + if (mUsbRegexs.length == 0) { + getPreferenceScreen().removePreference(mUsbTether); + + setTitle(R.string.tether_settings_title_wifi); + } + + mWifiRegexs = cm.getTetherableWifiRegexs(); + if (mWifiRegexs.length == 0) { + getPreferenceScreen().removePreference(mEnableWifiAp); + getPreferenceScreen().removePreference(mWifiApSettings); + + setTitle(R.string.tether_settings_title_usb); + } else if (mUsbRegexs.length != 0) { + // have both + setTitle(R.string.tether_settings_title_both); + } + mWifiApEnabler = new WifiApEnabler(this, mEnableWifiAp); + mView = new WebView(this); + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id == DIALOG_TETHER_HELP) { + Locale locale = Locale.getDefault(); + + // check for the full language + country resource, if not there, try just language + AssetManager am = getAssets(); + String path = HELP_PATH.replace("%y", locale.getLanguage().toLowerCase()); + path = path.replace("%z", "_"+locale.getCountry().toLowerCase()); + boolean useCountry = true; + InputStream is = null; + try { + is = am.open(path); + } catch (Exception e) { + useCountry = false; + } finally { + if (is != null) { + try { + is.close(); + } catch (Exception e) {} + } + } + String url = HELP_URL.replace("%y", locale.getLanguage().toLowerCase()); + url = url.replace("%z", (useCountry ? "_"+locale.getCountry().toLowerCase() : "")); + if ((mUsbRegexs.length != 0) && (mWifiRegexs.length == 0)) { + url = url.replace("%x", USB_HELP_MODIFIER); + } else if ((mWifiRegexs.length != 0) && (mUsbRegexs.length == 0)) { + url = url.replace("%x", WIFI_HELP_MODIFIER); + } else { + // could assert that both wifi and usb have regexs, but the default + // is to use this anyway so no check is needed + url = url.replace("%x", ""); + } + + mView.loadUrl(url); + + return new AlertDialog.Builder(this) + .setCancelable(true) + .setTitle(R.string.tethering_help_button_text) + .setView(mView) + .create(); + } + return null; + } + + private class TetherChangeReceiver extends BroadcastReceiver { + public void onReceive(Context content, Intent intent) { + if (intent.getAction().equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) { + // TODO - this should understand the interface types + ArrayList<String> available = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_AVAILABLE_TETHER); + ArrayList<String> active = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ACTIVE_TETHER); + ArrayList<String> errored = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ERRORED_TETHER); + updateState(available.toArray(), active.toArray(), errored.toArray()); + } else if (intent.getAction().equals(Intent.ACTION_MEDIA_SHARED) || + intent.getAction().equals(Intent.ACTION_MEDIA_UNSHARED)) { + updateState(); + } + } + } + + @Override + protected void onResume() { + super.onResume(); + + IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); + mTetherChangeReceiver = new TetherChangeReceiver(); + Intent intent = registerReceiver(mTetherChangeReceiver, filter); + + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_MEDIA_SHARED); + filter.addAction(Intent.ACTION_MEDIA_UNSHARED); + filter.addDataScheme("file"); + registerReceiver(mTetherChangeReceiver, filter); + + if (intent != null) mTetherChangeReceiver.onReceive(this, intent); + mWifiApEnabler.resume(); + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(mTetherChangeReceiver); + mTetherChangeReceiver = null; + mWifiApEnabler.pause(); + } + + private void updateState() { + ConnectivityManager cm = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + + String[] available = cm.getTetherableIfaces(); + String[] tethered = cm.getTetheredIfaces(); + String[] errored = cm.getTetheringErroredIfaces(); + updateState(available, tethered, errored); + } + + private void updateState(Object[] available, Object[] tethered, + Object[] errored) { + ConnectivityManager cm = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + boolean usbTethered = false; + boolean usbAvailable = false; + int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR; + boolean usbErrored = false; + boolean massStorageActive = + Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState()); + for (Object o : available) { + String s = (String)o; + for (String regex : mUsbRegexs) { + if (s.matches(regex)) { + usbAvailable = true; + if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { + usbError = cm.getLastTetherError(s); + } + } + } + } + for (Object o : tethered) { + String s = (String)o; + for (String regex : mUsbRegexs) { + if (s.matches(regex)) usbTethered = true; + } + } + for (Object o: errored) { + String s = (String)o; + for (String regex : mUsbRegexs) { + if (s.matches(regex)) usbErrored = true; + } + } + + if (usbTethered) { + mUsbTether.setSummary(R.string.usb_tethering_active_subtext); + mUsbTether.setEnabled(true); + mUsbTether.setChecked(true); + } else if (usbAvailable) { + if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { + mUsbTether.setSummary(R.string.usb_tethering_available_subtext); + } else { + mUsbTether.setSummary(R.string.usb_tethering_errored_subtext); + } + mUsbTether.setEnabled(true); + mUsbTether.setChecked(false); + } else if (usbErrored) { + mUsbTether.setSummary(R.string.usb_tethering_errored_subtext); + mUsbTether.setEnabled(false); + mUsbTether.setChecked(false); + } else if (massStorageActive) { + mUsbTether.setSummary(R.string.usb_tethering_storage_active_subtext); + mUsbTether.setEnabled(false); + mUsbTether.setChecked(false); + } else { + mUsbTether.setSummary(R.string.usb_tethering_unavailable_subtext); + mUsbTether.setEnabled(false); + mUsbTether.setChecked(false); + } + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + if (preference == mUsbTether) { + boolean newState = mUsbTether.isChecked(); + + ConnectivityManager cm = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + + if (newState) { + String[] available = cm.getTetherableIfaces(); + + String usbIface = findIface(available, mUsbRegexs); + if (usbIface == null) { + updateState(); + return true; + } + if (cm.tether(usbIface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + mUsbTether.setChecked(false); + mUsbTether.setSummary(R.string.usb_tethering_errored_subtext); + return true; + } + mUsbTether.setSummary(""); + } else { + String [] tethered = cm.getTetheredIfaces(); + + String usbIface = findIface(tethered, mUsbRegexs); + if (usbIface == null) { + updateState(); + return true; + } + if (cm.untether(usbIface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + mUsbTether.setSummary(R.string.usb_tethering_errored_subtext); + return true; + } + mUsbTether.setSummary(""); + } + } else if (preference == mTetherHelp) { + + showDialog(DIALOG_TETHER_HELP); + } + return false; + } + + private String findIface(String[] ifaces, String[] regexes) { + for (String iface : ifaces) { + for (String regex : regexes) { + if (iface.matches(regex)) { + return iface; + } + } + } + return null; + } +} diff --git a/src/com/android/settings/TextToSpeechSettings.java b/src/com/android/settings/TextToSpeechSettings.java index f60d0f2..89a4641 100644 --- a/src/com/android/settings/TextToSpeechSettings.java +++ b/src/com/android/settings/TextToSpeechSettings.java @@ -22,8 +22,11 @@ import static android.provider.Settings.Secure.TTS_DEFAULT_LANG; import static android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY; import static android.provider.Settings.Secure.TTS_DEFAULT_VARIANT; import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH; +import static android.provider.Settings.Secure.TTS_ENABLED_PLUGINS; +import android.app.AlertDialog; import android.content.ContentResolver; +import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; @@ -31,13 +34,17 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceActivity; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; import android.preference.CheckBoxPreference; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.speech.tts.TextToSpeech; import android.util.Log; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.StringTokenizer; @@ -48,6 +55,7 @@ public class TextToSpeechSettings extends PreferenceActivity 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"; @@ -55,6 +63,11 @@ public class TextToSpeechSettings extends PreferenceActivity implements private static final String KEY_TTS_DEFAULT_LANG = "tts_default_lang"; 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_PLUGIN_ENABLED_PREFIX = "ENABLED_"; + private static final String KEY_PLUGIN_SETTINGS_PREFIX = "SETTINGS_"; + // TODO move default Locale values to TextToSpeech.Engine private static final String DEFAULT_LANG_VAL = "eng"; private static final String DEFAULT_COUNTRY_VAL = "USA"; @@ -70,6 +83,7 @@ public class TextToSpeechSettings extends PreferenceActivity implements 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; @@ -82,14 +96,17 @@ public class TextToSpeechSettings extends PreferenceActivity implements private int mDemoStringIndex = 0; private boolean mEnableDemo = false; + private boolean mVoicesMissing = false; private TextToSpeech mTts = null; + private boolean mTtsStarted = false; /** * Request code (arbitrary value) for voice data check through * startActivityForResult. */ private static final int VOICE_DATA_INTEGRITY_CHECK = 1977; + private static final int GET_SAMPLE_TEXT = 1983; @Override protected void onCreate(Bundle savedInstanceState) { @@ -97,25 +114,35 @@ public class TextToSpeechSettings extends PreferenceActivity implements addPreferencesFromResource(R.xml.tts_settings); + addEngineSpecificSettings(); + mDemoStrings = getResources().getStringArray(R.array.tts_demo_strings); setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); mEnableDemo = false; - initClickers(); - initDefaultSettings(); + mTtsStarted = false; + + Locale currentLocale = Locale.getDefault(); + mDefaultLanguage = currentLocale.getISO3Language(); + mDefaultCountry = currentLocale.getISO3Country(); + mDefaultLocVariant = currentLocale.getVariant(); + + mTts = new TextToSpeech(this, this); } @Override protected void onStart() { super.onStart(); - // whenever we return to this screen, we don't know the state of the - // system, so we have to recheck that we can play the demo, or it must be disabled. - // TODO make the TTS service listen to "changes in the system", i.e. sd card un/mount - initClickers(); - updateWidgetState(); - checkVoiceData(); + if (mTtsStarted){ + // whenever we return to this screen, we don't know the state of the + // system, so we have to recheck that we can play the demo, or it must be disabled. + // TODO make the TTS service listen to "changes in the system", i.e. sd card un/mount + initClickers(); + updateWidgetState(); + checkVoiceData(); + } } @@ -127,6 +154,70 @@ public class TextToSpeechSettings extends PreferenceActivity implements } } + @Override + protected void onPause() { + super.onPause(); + if ((mDefaultRatePref != null) && (mDefaultRatePref.getDialog() != null)) { + mDefaultRatePref.getDialog().dismiss(); + } + if ((mDefaultLocPref != null) && (mDefaultLocPref.getDialog() != null)) { + mDefaultLocPref.getDialog().dismiss(); + } + if ((mDefaultSynthPref != null) && (mDefaultSynthPref.getDialog() != null)) { + mDefaultSynthPref.getDialog().dismiss(); + } + } + + + + private void addEngineSpecificSettings() { + 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(this); + 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(this); + 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 initClickers() { mPlayExample = findPreference(KEY_TTS_PLAY_EXAMPLE); @@ -156,7 +247,10 @@ public class TextToSpeechSettings extends PreferenceActivity implements mUseDefaultPref.setChecked(useDefault == 1); mUseDefaultPref.setOnPreferenceChangeListener(this); - // Default engine + // 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 @@ -225,16 +319,54 @@ public class TextToSpeechSettings extends PreferenceActivity implements } } + /** + * Ask the current default engine to return a string of sample text to be + * 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"); + 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); + } + } + } + /** * Called when the TTS engine is initialized. */ public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { - Log.v(TAG, "TTS engine for settings screen initialized."); mEnableDemo = true; - mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry)); + if (mDefaultLanguage == null) { + mDefaultLanguage = Locale.getDefault().getISO3Language(); + } + if (mDefaultCountry == null) { + mDefaultCountry = Locale.getDefault().getISO3Country(); + } + if (mDefaultLocVariant == null) { + mDefaultLocVariant = new String(); + } + mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); mTts.setSpeechRate((float)(mDefaultRate/100.0f)); + initDefaultSettings(); + initClickers(); + updateWidgetState(); + checkVoiceData(); + mTtsStarted = true; + Log.v(TAG, "TTS engine for settings screen initialized."); } else { Log.v(TAG, "TTS engine for settings screen failed to initialize successfully."); mEnableDemo = false; @@ -248,15 +380,114 @@ public class TextToSpeechSettings extends PreferenceActivity implements */ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == VOICE_DATA_INTEGRITY_CHECK) { - if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) { - Log.v(TAG, "Voice data check passed"); + 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; + } + if (available.size() > 0){ if (mTts == null) { mTts = new TextToSpeech(this, 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( + 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){ + 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 { - Log.v(TAG, "Voice data check failed"); mEnableDemo = false; - updateWidgetState(); + } + + if (unavailable.size() > 0){ + mVoicesMissing = true; + } else { + mVoicesMissing = false; + } + + updateWidgetState(); + } else if (requestCode == GET_SAMPLE_TEXT) { + if (resultCode == TextToSpeech.LANG_AVAILABLE) { + String sample = 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); + } + } else { + // TODO: Display an error here to the user. + Log.e(TAG, "Did not have a sample string for the requested language"); } } } @@ -292,11 +523,22 @@ public class TextToSpeechSettings extends PreferenceActivity implements Log.v(TAG, "TTS default lang/country/variant set to " + mDefaultLanguage + "/" + mDefaultCountry + "/" + mDefaultLocVariant); if (mTts != null) { - mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry)); + mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant)); } int newIndex = mDefaultLocPref.findIndexOfValue((String)objValue); Log.v("Settings", " 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(); + } + Log.v("Settings", "The default synth is: " + objValue.toString()); } return true; @@ -308,10 +550,9 @@ public class TextToSpeechSettings extends PreferenceActivity implements */ public boolean onPreferenceClick(Preference preference) { if (preference == mPlayExample) { - // Play example - if (mTts != null) { - mTts.speak(mDemoStrings[mDemoStringIndex], TextToSpeech.QUEUE_FLUSH, null); - } + // Get the sample text from the TTS engine; onActivityResult will do + // the actual speaking + getSampleText(); return true; } if (preference == mInstallData) { @@ -323,6 +564,46 @@ public class TextToSpeechSettings extends PreferenceActivity implements 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(this)) + .setTitle(android.R.string.dialog_alert_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(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); @@ -330,7 +611,7 @@ public class TextToSpeechSettings extends PreferenceActivity implements mDefaultRatePref.setEnabled(mEnableDemo); mDefaultLocPref.setEnabled(mEnableDemo); - mInstallData.setEnabled(!mEnableDemo); + mInstallData.setEnabled(mVoicesMissing); } @@ -372,13 +653,15 @@ public class TextToSpeechSettings extends PreferenceActivity implements // demo string (at this stage there is a default language pref) ContentResolver resolver = getContentResolver(); mDefaultLanguage = Settings.Secure.getString(resolver, TTS_DEFAULT_LANG); - mDefaultCountry = Settings.Secure.getString(resolver, KEY_TTS_DEFAULT_COUNTRY); - mDefaultLocVariant = Settings.Secure.getString(resolver, KEY_TTS_DEFAULT_VARIANT); + mDefaultCountry = Settings.Secure.getString(resolver, TTS_DEFAULT_COUNTRY); + mDefaultLocVariant = Settings.Secure.getString(resolver, TTS_DEFAULT_VARIANT); // update the demo string mDemoStringIndex = mDefaultLocPref.findIndexOfValue(mDefaultLanguage + LOCALE_DELIMITER + mDefaultCountry); - mDefaultLocPref.setValueIndex(mDemoStringIndex); + if (mDemoStringIndex > -1){ + mDefaultLocPref.setValueIndex(mDemoStringIndex); + } } /** @@ -386,8 +669,20 @@ public class TextToSpeechSettings extends PreferenceActivity implements * Returns whether there is a default language in the TTS settings. */ private boolean hasLangPref() { - String language = Settings.Secure.getString(getContentResolver(), TTS_DEFAULT_LANG); - return (language != null); + ContentResolver resolver = getContentResolver(); + String language = Settings.Secure.getString(resolver, TTS_DEFAULT_LANG); + if ((language == null) || (language.length() < 1)) { + return false; + } + String country = Settings.Secure.getString(resolver, TTS_DEFAULT_COUNTRY); + if (country == null) { + return false; + } + String variant = Settings.Secure.getString(resolver, TTS_DEFAULT_VARIANT); + if (variant == null) { + return false; + } + return true; } /** @@ -424,4 +719,50 @@ public class TextToSpeechSettings extends PreferenceActivity 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 + " "; + } + } + } + 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)); + + // 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); + } + mDefaultSynthPref.setValueIndex(selectedEngineIndex); + } + } diff --git a/src/com/android/settings/UserDictionarySettings.java b/src/com/android/settings/UserDictionarySettings.java index 4b30b53..6ffcb3d 100644 --- a/src/com/android/settings/UserDictionarySettings.java +++ b/src/com/android/settings/UserDictionarySettings.java @@ -159,7 +159,8 @@ public class UserDictionarySettings extends ListActivity { AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo; String word = getWord(adapterMenuInfo.position); - + if (word == null) return true; + switch (item.getItemId()) { case CONTEXT_MENU_DELETE: deleteWord(word); @@ -193,6 +194,9 @@ public class UserDictionarySettings extends ListActivity { private String getWord(int position) { mCursor.moveToPosition(position); + // Handle a possible race-condition + if (mCursor.isAfterLast()) return null; + return mCursor.getString( mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD)); } diff --git a/src/com/android/settings/VoiceInputOutputSettings.java b/src/com/android/settings/VoiceInputOutputSettings.java new file mode 100644 index 0000000..30bedda --- /dev/null +++ b/src/com/android/settings/VoiceInputOutputSettings.java @@ -0,0 +1,228 @@ +/* + * 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 org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.preference.Preference.OnPreferenceChangeListener; +import android.provider.Settings; +import android.speech.RecognitionService; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; + +/** + * Settings screen for voice input/output. + */ +public class VoiceInputOutputSettings extends PreferenceActivity + implements OnPreferenceChangeListener { + + private static final String TAG = "VoiceInputOutputSettings"; + + private static final String KEY_PARENT = "parent"; + private static final String KEY_VOICE_INPUT_CATEGORY = "voice_input_category"; + private static final String KEY_RECOGNIZER = "recognizer"; + private static final String KEY_RECOGNIZER_SETTINGS = "recognizer_settings"; + + private PreferenceGroup mParent; + private PreferenceCategory mVoiceInputCategory; + private ListPreference mRecognizerPref; + private PreferenceScreen mSettingsPref; + + private HashMap<String, ResolveInfo> mAvailableRecognizersMap; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + addPreferencesFromResource(R.xml.voice_input_output_settings); + + mParent = (PreferenceGroup) findPreference(KEY_PARENT); + mVoiceInputCategory = (PreferenceCategory) mParent.findPreference(KEY_VOICE_INPUT_CATEGORY); + mRecognizerPref = (ListPreference) mParent.findPreference(KEY_RECOGNIZER); + mRecognizerPref.setOnPreferenceChangeListener(this); + mSettingsPref = (PreferenceScreen) mParent.findPreference(KEY_RECOGNIZER_SETTINGS); + + mAvailableRecognizersMap = new HashMap<String, ResolveInfo>(); + + populateOrRemoveRecognizerPreference(); + } + + private void populateOrRemoveRecognizerPreference() { + List<ResolveInfo> availableRecognitionServices = getPackageManager().queryIntentServices( + new Intent(RecognitionService.SERVICE_INTERFACE), PackageManager.GET_META_DATA); + int numAvailable = availableRecognitionServices.size(); + + if (numAvailable == 0) { + // No recognizer available - remove all related preferences. + removePreference(mVoiceInputCategory); + removePreference(mRecognizerPref); + removePreference(mSettingsPref); + } else if (numAvailable == 1) { + // Only one recognizer available, so don't show the list of choices, but do + // set up the link to settings for the available recognizer. + removePreference(mRecognizerPref); + + // But first set up the available recognizers map with just the one recognizer. + ResolveInfo resolveInfo = availableRecognitionServices.get(0); + String recognizerComponent = + new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name).flattenToShortString(); + + mAvailableRecognizersMap.put(recognizerComponent, resolveInfo); + + String currentSetting = Settings.Secure.getString( + getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); + updateSettingsLink(currentSetting); + } else { + // Multiple recognizers available, so show the full list of choices. + populateRecognizerPreference(availableRecognitionServices); + } + } + + private void removePreference(Preference pref) { + if (pref != null) { + mParent.removePreference(pref); + } + } + + private void populateRecognizerPreference(List<ResolveInfo> recognizers) { + int size = recognizers.size(); + CharSequence[] entries = new CharSequence[size]; + CharSequence[] values = new CharSequence[size]; + + // Get the current value from the secure setting. + String currentSetting = Settings.Secure.getString( + getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE); + + // Iterate through all the available recognizers and load up their info to show + // in the preference. Also build up a map of recognizer component names to their + // ResolveInfos - we'll need that a little later. + for (int i = 0; i < size; i++) { + ResolveInfo resolveInfo = recognizers.get(i); + String recognizerComponent = + new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name).flattenToShortString(); + + mAvailableRecognizersMap.put(recognizerComponent, resolveInfo); + + entries[i] = resolveInfo.loadLabel(getPackageManager()); + values[i] = recognizerComponent; + } + + mRecognizerPref.setEntries(entries); + mRecognizerPref.setEntryValues(values); + + mRecognizerPref.setDefaultValue(currentSetting); + mRecognizerPref.setValue(currentSetting); + + updateSettingsLink(currentSetting); + } + + private void updateSettingsLink(String currentSetting) { + ResolveInfo currentRecognizer = mAvailableRecognizersMap.get(currentSetting); + ServiceInfo si = currentRecognizer.serviceInfo; + XmlResourceParser parser = null; + String settingsActivity = null; + try { + parser = si.loadXmlMetaData(getPackageManager(), RecognitionService.SERVICE_META_DATA); + if (parser == null) { + throw new XmlPullParserException("No " + RecognitionService.SERVICE_META_DATA + + " meta-data for " + si.packageName); + } + + Resources res = getPackageManager().getResourcesForApplication( + si.applicationInfo); + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"recognition-service".equals(nodeName)) { + throw new XmlPullParserException( + "Meta-data does not start with recognition-service tag"); + } + + TypedArray array = res.obtainAttributes(attrs, + com.android.internal.R.styleable.RecognitionService); + settingsActivity = array.getString( + com.android.internal.R.styleable.RecognitionService_settingsActivity); + array.recycle(); + } catch (XmlPullParserException e) { + Log.e(TAG, "error parsing recognition service meta-data", e); + } catch (IOException e) { + Log.e(TAG, "error parsing recognition service meta-data", e); + } catch (NameNotFoundException e) { + Log.e(TAG, "error parsing recognition service meta-data", e); + } finally { + if (parser != null) parser.close(); + } + + if (settingsActivity == null) { + // No settings preference available - hide the preference. + Log.w(TAG, "no recognizer settings available for " + si.packageName); + mSettingsPref.setIntent(null); + mParent.removePreference(mSettingsPref); + } else { + Intent i = new Intent(Intent.ACTION_MAIN); + i.setComponent(new ComponentName(si.packageName, settingsActivity)); + mSettingsPref.setIntent(i); + mRecognizerPref.setSummary(currentRecognizer.loadLabel(getPackageManager())); + } + } + + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mRecognizerPref) { + String setting = (String) newValue; + + // Put the new value back into secure settings. + Settings.Secure.putString( + getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE, + setting); + + // Update the settings item so it points to the right settings. + updateSettingsLink(setting); + } + return true; + } +} diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java index 22417bb..78cf8cf 100644 --- a/src/com/android/settings/WirelessSettings.java +++ b/src/com/android/settings/WirelessSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2009 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. @@ -17,10 +17,11 @@ package com.android.settings; import android.bluetooth.BluetoothAdapter; +import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Bundle; -import android.os.IBinder; import android.os.ServiceManager; import android.os.SystemProperties; import android.preference.CheckBoxPreference; @@ -28,6 +29,7 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.provider.Settings; +import android.util.Log; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; @@ -42,13 +44,14 @@ public class WirelessSettings extends PreferenceActivity { 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"; public static final String EXIT_ECM_RESULT = "exit_ecm_result"; public static final int REQUEST_CODE_EXIT_ECM = 1; - private WifiEnabler mWifiEnabler; private AirplaneModeEnabler mAirplaneModeEnabler; - private BluetoothEnabler mBtEnabler; private CheckBoxPreference mAirplaneModePreference; + private WifiEnabler mWifiEnabler; + private BluetoothEnabler mBtEnabler; /** * Invoked on each preference click in this hierarchy, overrides @@ -57,96 +60,113 @@ public class WirelessSettings extends PreferenceActivity { */ @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - if ( (preference == mAirplaneModePreference) && - (Boolean.parseBoolean( - SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) ) { + if (preference == mAirplaneModePreference && Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { // In ECM mode launch ECM app dialog startActivityForResult( new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null), REQUEST_CODE_EXIT_ECM); - return true; } - else { - // Let the intents be launched by the Preference manager - return false; + // Let the intents be launched by the Preference manager + return false; + } + + public static boolean isRadioAllowed(Context context, String type) { + if (!AirplaneModeEnabler.isAirplaneModeOn(context)) { + return true; } + // Here we use the same logic in onCreate(). + String toggleable = Settings.System.getString(context.getContentResolver(), + Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + return toggleable != null && toggleable.contains(type); } - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.wireless_settings); - initToggles(); + CheckBoxPreference airplane = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); + CheckBoxPreference wifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI); + CheckBoxPreference bt = (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH); + + mAirplaneModeEnabler = new AirplaneModeEnabler(this, airplane); mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); + mWifiEnabler = new WifiEnabler(this, wifi); + mBtEnabler = new BluetoothEnabler(this, bt); + + String toggleable = Settings.System.getString(getContentResolver(), + Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + + // 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); + } + + // Disable Bluetooth Settings if Bluetooth service is not available. + if (ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE) == null) { + findPreference(KEY_BT_SETTINGS).setEnabled(false); + } + + // Disable Tethering if it's not allowed + ConnectivityManager cm = + (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); + if (!cm.isTetheringSupported()) { + getPreferenceScreen().removePreference(findPreference(KEY_TETHER_SETTINGS)); + } else { + String[] usbRegexs = cm.getTetherableUsbRegexs(); + String[] wifiRegexs = cm.getTetherableWifiRegexs(); + Preference p = findPreference(KEY_TETHER_SETTINGS); + if (wifiRegexs.length == 0) { + p.setTitle(R.string.tether_settings_title_usb); + p.setSummary(R.string.tether_settings_summary_usb); + } else { + if (usbRegexs.length == 0) { + p.setTitle(R.string.tether_settings_title_wifi); + p.setSummary(R.string.tether_settings_summary_wifi); + } else { + p.setTitle(R.string.tether_settings_title_both); + p.setSummary(R.string.tether_settings_summary_both); + } + } + } } - + @Override protected void onResume() { super.onResume(); + mAirplaneModeEnabler.resume(); mWifiEnabler.resume(); mBtEnabler.resume(); - mAirplaneModeEnabler.resume(); } @Override protected void onPause() { super.onPause(); - mWifiEnabler.pause(); mAirplaneModeEnabler.pause(); + mWifiEnabler.pause(); mBtEnabler.pause(); } - private void initToggles() { - - Preference airplanePreference = findPreference(KEY_TOGGLE_AIRPLANE); - Preference wifiPreference = findPreference(KEY_TOGGLE_WIFI); - Preference btPreference = findPreference(KEY_TOGGLE_BLUETOOTH); - Preference wifiSettings = findPreference(KEY_WIFI_SETTINGS); - Preference vpnSettings = findPreference(KEY_VPN_SETTINGS); - - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); - if (b == null) { - // Disable BT Settings if BT service is not available. - Preference btSettings = findPreference(KEY_BT_SETTINGS); - btSettings.setEnabled(false); - } - - mWifiEnabler = new WifiEnabler( - this, (WifiManager) getSystemService(WIFI_SERVICE), - (CheckBoxPreference) wifiPreference); - mAirplaneModeEnabler = new AirplaneModeEnabler( - this, (CheckBoxPreference) airplanePreference); - mBtEnabler = new BluetoothEnabler(this, (CheckBoxPreference) btPreference); - - // manually set up dependencies for Wifi if its radio is not toggleable in airplane mode - String toggleableRadios = Settings.System.getString(getContentResolver(), - Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); - if (toggleableRadios == null || !toggleableRadios.contains(Settings.System.RADIO_WIFI)) { - wifiPreference.setDependency(airplanePreference.getKey()); - wifiSettings.setDependency(airplanePreference.getKey()); - vpnSettings.setDependency(airplanePreference.getKey()); - } - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch(requestCode) { - case REQUEST_CODE_EXIT_ECM: - Boolean isChoiceYes = - data.getBooleanExtra(EXIT_ECM_RESULT, false); + if (requestCode == REQUEST_CODE_EXIT_ECM) { + Boolean isChoiceYes = data.getBooleanExtra(EXIT_ECM_RESULT, false); // Set Airplane mode based on the return value and checkbox state mAirplaneModeEnabler.setAirplaneModeInECM(isChoiceYes, mAirplaneModePreference.isChecked()); - break; - - default: - break; } } - } diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index b872916..426a4d3 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -17,6 +17,7 @@ package com.android.settings.bluetooth; import com.android.settings.R; +import com.android.settings.WirelessSettings; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; @@ -25,8 +26,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.preference.Preference; import android.preference.CheckBoxPreference; +import android.provider.Settings; import android.text.TextUtils; -import android.util.Config; +import android.widget.Toast; /** * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox @@ -34,16 +36,12 @@ import android.util.Config; * preference reflects the current state. */ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { - - private static final boolean LOCAL_LOGD = Config.LOGD || false; - private static final String TAG = "BluetoothEnabler"; - private final Context mContext; - private final CheckBoxPreference mCheckBoxPreference; + private final CheckBoxPreference mCheckBox; private final CharSequence mOriginalSummary; private final LocalBluetoothManager mLocalManager; - + private final IntentFilter mIntentFilter; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -52,18 +50,18 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { } }; - public BluetoothEnabler(Context context, CheckBoxPreference checkBoxPreference) { + public BluetoothEnabler(Context context, CheckBoxPreference checkBox) { mContext = context; - mCheckBoxPreference = checkBoxPreference; - - mOriginalSummary = checkBoxPreference.getSummary(); - checkBoxPreference.setPersistent(false); + mCheckBox = checkBox; + mOriginalSummary = checkBox.getSummary(); + checkBox.setPersistent(false); mLocalManager = LocalBluetoothManager.getInstance(context); if (mLocalManager == null) { - // Bluetooth not supported - checkBoxPreference.setEnabled(false); + // Bluetooth is not supported + checkBox.setEnabled(false); } + mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); } public void resume() { @@ -71,16 +69,11 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { return; } - int state = mLocalManager.getBluetoothState(); - // This is the widget enabled state, not the preference toggled state - mCheckBoxPreference.setEnabled(state == BluetoothAdapter.STATE_ON || - state == BluetoothAdapter.STATE_OFF); - // BT state is not a sticky broadcast, so set it manually - handleStateChanged(state); + // Bluetooth state is not sticky, so set it manually + handleStateChanged(mLocalManager.getBluetoothState()); - mContext.registerReceiver(mReceiver, - new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); - mCheckBoxPreference.setOnPreferenceChangeListener(this); + mContext.registerReceiver(mReceiver, mIntentFilter); + mCheckBox.setOnPreferenceChangeListener(this); } public void pause() { @@ -89,72 +82,51 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { } mContext.unregisterReceiver(mReceiver); - mCheckBoxPreference.setOnPreferenceChangeListener(null); + mCheckBox.setOnPreferenceChangeListener(null); } public boolean onPreferenceChange(Preference preference, Object value) { - // Turn on/off BT - setEnabled((Boolean) value); + boolean enable = (Boolean) value; + + // 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; + } + + mLocalManager.setBluetoothEnabled(enable); + mCheckBox.setEnabled(false); // Don't update UI to opposite state until we're sure return false; } - private void setEnabled(final boolean enable) { - // Disable preference - mCheckBoxPreference.setEnabled(false); - - mLocalManager.setBluetoothEnabled(enable); - } - private void handleStateChanged(int state) { - - if (state == BluetoothAdapter.STATE_OFF || - state == BluetoothAdapter.STATE_ON) { - mCheckBoxPreference.setChecked(state == BluetoothAdapter.STATE_ON); - mCheckBoxPreference.setSummary(state == BluetoothAdapter.STATE_OFF ? - mOriginalSummary : - null); - - /* - * Don't ever disable the preference. Only enable here. Disablement - * is taken care of by the dependency code. If this is disabled - * here, it may not be re-enabled from the framework when dependency - * is met. http://b/issue?id=2053751 - */ - if (isEnabledByDependency()) { - mCheckBoxPreference.setEnabled(true); - } - - } else if (state == BluetoothAdapter.STATE_TURNING_ON || - state == BluetoothAdapter.STATE_TURNING_OFF) { - mCheckBoxPreference.setSummary(state == BluetoothAdapter.STATE_TURNING_ON - ? R.string.wifi_starting - : R.string.wifi_stopping); - - } else { - mCheckBoxPreference.setChecked(false); - mCheckBoxPreference.setSummary(R.string.wifi_error); - mCheckBoxPreference.setEnabled(true); - } - } - - private boolean isEnabledByDependency() { - Preference dep = getDependencyPreference(); - if (dep == null) { - return true; - } - - return !dep.shouldDisableDependents(); - } - - private Preference getDependencyPreference() { - String depKey = mCheckBoxPreference.getDependency(); - if (TextUtils.isEmpty(depKey)) { - return null; + switch (state) { + case BluetoothAdapter.STATE_TURNING_ON: + mCheckBox.setSummary(R.string.wifi_starting); + mCheckBox.setEnabled(false); + break; + case BluetoothAdapter.STATE_ON: + mCheckBox.setChecked(true); + mCheckBox.setSummary(null); + mCheckBox.setEnabled(true); + break; + case BluetoothAdapter.STATE_TURNING_OFF: + mCheckBox.setSummary(R.string.wifi_stopping); + mCheckBox.setEnabled(false); + break; + case BluetoothAdapter.STATE_OFF: + mCheckBox.setChecked(false); + mCheckBox.setSummary(mOriginalSummary); + mCheckBox.setEnabled(true); + break; + default: + mCheckBox.setChecked(false); + mCheckBox.setSummary(R.string.wifi_error); + mCheckBox.setEnabled(true); } - - return mCheckBoxPreference.getPreferenceManager().findPreference(depKey); } - } diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java index c1a2116..dbdf6c1 100644 --- a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java +++ b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java @@ -16,6 +16,9 @@ package com.android.settings.bluetooth; +import com.android.settings.R; +import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; + import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -25,11 +28,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.util.Log; -import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - /** * BluetoothEventRedirector receives broadcasts and callbacks from the Bluetooth * API and dispatches the event on the UI thread to the right class in the @@ -53,9 +54,11 @@ public class BluetoothEventRedirector { BluetoothAdapter.ERROR); mManager.setBluetoothStateInt(state); } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) { + persistDiscoveringTimestamp(); mManager.onScanningStateChanged(true); } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { + persistDiscoveringTimestamp(); mManager.onScanningStateChanged(false); } else if (action.equals(BluetoothDevice.ACTION_FOUND)) { @@ -191,4 +194,11 @@ public class BluetoothEventRedirector { } return null; } + + private void persistDiscoveringTimestamp() { + SharedPreferences.Editor editor = mManager.getSharedPreferences().edit(); + editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, + System.currentTimeMillis()); + editor.commit(); + } } diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java index 4364795..1e73b2d 100644 --- a/src/com/android/settings/bluetooth/BluetoothSettings.java +++ b/src/com/android/settings/bluetooth/BluetoothSettings.java @@ -35,7 +35,6 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.view.ContextMenu; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ContextMenu.ContextMenuInfo; @@ -53,8 +52,6 @@ public class BluetoothSettings extends PreferenceActivity private static final String TAG = "BluetoothSettings"; - private static final int MENU_SCAN = Menu.FIRST; - private static final String KEY_BT_CHECKBOX = "bt_checkbox"; private static final String KEY_BT_DISCOVERABLE = "bt_discoverable"; private static final String KEY_BT_DEVICE_LIST = "bt_device_list"; @@ -202,6 +199,12 @@ public class BluetoothSettings extends PreferenceActivity } } + @Override + protected void onUserLeaveHint() { + super.onUserLeaveHint(); + mLocalManager.stopScanning(); + } + private void addDevices() { List<CachedBluetoothDevice> cachedDevices = mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); @@ -211,33 +214,6 @@ public class BluetoothSettings extends PreferenceActivity } @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, MENU_SCAN, 0, R.string.bluetooth_scan_for_devices) - .setIcon(com.android.internal.R.drawable.ic_menu_refresh) - .setAlphabeticShortcut('r'); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.findItem(MENU_SCAN).setEnabled(mLocalManager.getBluetoothAdapter().isEnabled()); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case MENU_SCAN: - mLocalManager.startScanning(true); - return true; - - default: - return false; - } - } - - @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { @@ -254,6 +230,7 @@ public class BluetoothSettings extends PreferenceActivity CachedBluetoothDevice device = btPreference.getCachedDevice(); mSelectedDevice = device.getDevice(); + mLocalManager.stopScanning(); mLocalManager.persistSelectedDeviceInPicker(mSelectedDevice.getAddress()); if ((device.getBondState() == BluetoothDevice.BOND_BONDED) || (mNeedAuth == false)) { diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java index 57bffa9..aa4a958 100644 --- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java +++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java @@ -95,7 +95,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private static final long MAX_WAIT_TIME_FOR_FRAMEWORK = 25 * 1000; private enum BluetoothCommand { - CONNECT, DISCONNECT, + CONNECT, DISCONNECT, REMOVE_BOND, } static class BluetoothJob { @@ -118,7 +118,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> StringBuilder sb = new StringBuilder(); sb.append(command.name()); sb.append(" Address:").append(cachedDevice.mDevice); - sb.append(" Profile:").append(profile.name()); + if (profile != null) { + sb.append(" Profile:").append(profile.name()); + } sb.append(" TimeSent:"); if (timeSent == 0) { sb.append("not yet"); @@ -210,6 +212,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> case DISCONNECT: successful = disconnectInt(job.cachedDevice, job.profile); break; + case REMOVE_BOND: + BluetoothDevice dev = job.cachedDevice.getDevice(); + if (dev != null) { + successful = dev.removeBond(); + } + break; } if (successful) { @@ -510,32 +518,16 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } public void unpair() { - synchronized (workQueue) { - // Remove any pending commands for this device - boolean processNow = false; - Iterator<BluetoothJob> it = workQueue.iterator(); - while (it.hasNext()) { - BluetoothJob job = it.next(); - if (job.cachedDevice.mDevice.equals(this.mDevice)) { - it.remove(); - if (job.timeSent != 0) { - processNow = true; - } - } - } - if (processNow) { - processCommands(); - } - } + disconnect(); - switch (getBondState()) { - case BluetoothDevice.BOND_BONDED: - mDevice.removeBond(); - break; + int state = getBondState(); - case BluetoothDevice.BOND_BONDING: + if (state == BluetoothDevice.BOND_BONDING) { mDevice.cancelBondProcess(); - break; + } + + if (state != BluetoothDevice.BOND_NONE) { + queueCommand(new BluetoothJob(BluetoothCommand.REMOVE_BOND, this, null)); } } @@ -757,7 +749,32 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> public void onBondingStateChanged(int bondState) { if (bondState == BluetoothDevice.BOND_NONE) { mProfiles.clear(); + + BluetoothJob job = workQueue.peek(); + if (job != null) { + // Remove the first item and process the next one + if (job.command == BluetoothCommand.REMOVE_BOND + && job.cachedDevice.mDevice.equals(mDevice)) { + workQueue.poll(); // dequeue + } else { + // Unexpected job + if (D) { + Log.d(TAG, "job.command = " + job.command); + Log.d(TAG, "mDevice:" + mDevice + " != head:" + job.toString()); + } + + // Check to see if we need to remove the stale items from the queue + if (!pruneQueue(null)) { + // nothing in the queue was modify. Just ignore the notification and return. + refresh(); + return; + } + } + + processCommands(); + } } + refresh(); } diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java index 7906d79..4497480 100644 --- a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java +++ b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java @@ -43,7 +43,6 @@ public class CachedBluetoothDeviceManager { public CachedBluetoothDeviceManager(LocalBluetoothManager localManager) { mLocalManager = localManager; mCallbacks = localManager.getCallbacks(); - readPairedDevices(); } private synchronized boolean readPairedDevices() { diff --git a/src/com/android/settings/bluetooth/DockEventReceiver.java b/src/com/android/settings/bluetooth/DockEventReceiver.java index 2d634b2..6d11972 100644 --- a/src/com/android/settings/bluetooth/DockEventReceiver.java +++ b/src/com/android/settings/bluetooth/DockEventReceiver.java @@ -16,12 +16,17 @@ package com.android.settings.bluetooth; +import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; + import android.app.Service; +import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.PowerManager; import android.util.Log; @@ -75,6 +80,54 @@ public class DockEventReceiver extends BroadcastReceiver { if (DEBUG) Log.e(TAG, "Unknown state"); break; } + } else if (BluetoothHeadset.ACTION_STATE_CHANGED.equals(intent.getAction())) { + /* + * Reconnect to the dock if: + * 1) it is a dock + * 2) it is disconnected + * 3) the disconnect is initiated remotely + * 4) the dock is still docked (check can only be done in the Service) + */ + if (device == null) { + if (DEBUG) Log.d(TAG, "Device is missing"); + return; + } + + int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, + BluetoothHeadset.STATE_CONNECTED); + if (newState != BluetoothHeadset.STATE_DISCONNECTED) return; + + int source = intent.getIntExtra(BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR, + BluetoothHeadset.LOCAL_DISCONNECT); + if (source != BluetoothHeadset.REMOTE_DISCONNECT) return; + + // Too bad, the dock state can't be checked from a BroadcastReceiver. + Intent i = new Intent(intent); + i.setClass(context, DockService.class); + beginStartingService(context, i); + + } else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(intent.getAction())) { + /* + * Reconnect to the dock if: + * 1) it is a dock + * 2) it is an unexpected disconnect i.e. didn't go through disconnecting state + * 3) the dock is still docked (check can only be done in the Service) + */ + if (device == null) { + if (DEBUG) Log.d(TAG, "Device is missing"); + return; + } + + int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); + int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0); + if (newState == BluetoothA2dp.STATE_DISCONNECTED && + oldState != BluetoothA2dp.STATE_DISCONNECTING) { + // Too bad, the dock state can't be checked from a BroadcastReceiver. + Intent i = new Intent(intent); + i.setClass(context, DockService.class); + beginStartingService(context, i); + } + } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (btState != BluetoothAdapter.STATE_TURNING_ON) { diff --git a/src/com/android/settings/bluetooth/DockService.java b/src/com/android/settings/bluetooth/DockService.java index 1425e23..f318987 100644 --- a/src/com/android/settings/bluetooth/DockService.java +++ b/src/com/android/settings/bluetooth/DockService.java @@ -87,6 +87,15 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli private static final String SHARED_PREFERENCES_KEY_DISABLE_BT = "disable_bt"; + private static final String SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT = + "connect_retry_count"; + + /* + * If disconnected unexpectedly, reconnect up to 6 times. Each profile counts + * as one time so it's only 3 times for both profiles on the car dock. + */ + private static final int MAX_CONNECT_RETRY = 6; + private static final int INVALID_STARTID = -100; // Created in OnCreate() @@ -161,6 +170,32 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return START_NOT_STICKY; } + /* + * This assumes that the intent sender has checked that this is a dock + * and that the intent is for a disconnect + */ + if (BluetoothHeadset.ACTION_STATE_CHANGED.equals(intent.getAction())) { + BluetoothDevice disconnectedDevice = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0); + if (retryCount < MAX_CONNECT_RETRY) { + setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1); + handleUnexpectedDisconnect(disconnectedDevice, Profile.HEADSET, startId); + } + return START_NOT_STICKY; + } else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(intent.getAction())) { + BluetoothDevice disconnectedDevice = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0); + if (retryCount < MAX_CONNECT_RETRY) { + setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1); + handleUnexpectedDisconnect(disconnectedDevice, Profile.A2DP, startId); + } + return START_NOT_STICKY; + } + Message msg = parseIntent(intent); if (msg == null) { // Bad intent @@ -169,6 +204,10 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return START_NOT_STICKY; } + if (msg.what == MSG_TYPE_DOCKED) { + removeSetting(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT); + } + msg.arg2 = startId; processMessage(msg); @@ -248,10 +287,10 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli if (DEBUG) { Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = " - + getSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); + + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); } - if (getSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)) { + if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)) { // BT was disabled when we first docked if (!hasOtherConnectedDevices(device)) { if(DEBUG) Log.d(TAG, "QUEUED BT DISABLE"); @@ -280,7 +319,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED); } else { // disable() returned an error. Persist a flag to disable BT later - setSetting(SHARED_PREFERENCES_KEY_DISABLE_BT, true); + setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT, true); mPendingTurnOffStartId = startId; deferFinishCall = true; if(DEBUG) Log.d(TAG, "disable failed. try again later " + startId); @@ -509,7 +548,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } else { if (DEBUG) { Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = " - + getSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); + + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); } // Reconnect if docked and bluetooth was enabled by user. Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); @@ -522,7 +561,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli if (device != null) { connectIfEnabled(device); } - } else if (getSetting(SHARED_PREFERENCES_KEY_DISABLE_BT) + } else if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT) && mBtManager.getBluetoothAdapter().disable()) { mPendingTurnOffStartId = startId; removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT); @@ -565,6 +604,34 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } } + private void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice, Profile profile, + int startId) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "handling failed connect for " + disconnectedDevice); + + // Reconnect if docked. + if (disconnectedDevice != null) { + // registerReceiver can't be called from a BroadcastReceiver + Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (i != null) { + int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + BluetoothDevice dockedDevice = i + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (dockedDevice != null && dockedDevice.equals(disconnectedDevice)) { + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, + mBtManager, dockedDevice); + cachedDevice.connect(profile); + } + } + } + } + + DockEventReceiver.finishStartingService(this, startId); + } + } + private synchronized void connectIfEnabled(BluetoothDevice device) { CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager, device); List<Profile> profiles = cachedDevice.getConnectableProfiles(); @@ -612,7 +679,8 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli mPendingDevice = device; mPendingStartId = startId; if (btState != BluetoothAdapter.STATE_TURNING_ON) { - setSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED, true); + setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED, + true); } return; } @@ -676,16 +744,29 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return cachedBluetoothDevice; } - private boolean getSetting(String key) { + private boolean getSettingBool(String key) { SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); return sharedPref.getBoolean(key, false); } - private void setSetting(String key, boolean disableBt) { + private int getSettingInt(String key, int defaultValue) { + SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, + Context.MODE_PRIVATE); + return sharedPref.getInt(key, defaultValue); + } + + private void setSettingBool(String key, boolean bool) { + SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, + Context.MODE_PRIVATE).edit(); + editor.putBoolean(key, bool); + editor.commit(); + } + + private void setSettingInt(String key, int value) { SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE).edit(); - editor.putBoolean(key, disableBt); + editor.putInt(key, value); editor.commit(); } diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java index ec5c6bc..2ffb139 100644 --- a/src/com/android/settings/bluetooth/LocalBluetoothManager.java +++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java @@ -72,6 +72,9 @@ public class LocalBluetoothManager { // of raising notifications private static long GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; + public static final String SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP = + "last_discovering_time"; + private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE = "last_selected_device"; @@ -198,6 +201,12 @@ public class LocalBluetoothManager { } } + public void stopScanning() { + if (mAdapter.isDiscovering()) { + mAdapter.cancelDiscovery(); + } + } + public int getBluetoothState() { if (mState == BluetoothAdapter.ERROR) { @@ -308,7 +317,7 @@ public class LocalBluetoothManager { long currentTimeMillis = System.currentTimeMillis(); SharedPreferences sharedPreferences = getSharedPreferences(); - // If the device was in discoverable mode recently + // If the device was in discoverABLE mode recently long lastDiscoverableEndTime = sharedPreferences.getLong( BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0); if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) @@ -316,6 +325,14 @@ public class LocalBluetoothManager { return true; } + // If the device was discoverING recently + if (mAdapter != null && mAdapter.isDiscovering()) { + return true; + } else if ((sharedPreferences.getLong(SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, 0) + + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) { + return true; + } + // If the device was picked in the device picker recently if (deviceAddress != null) { String lastSelectedDevice = sharedPreferences.getString( diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java index 9fdb338..eca233c 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java @@ -16,11 +16,10 @@ package com.android.settings.bluetooth; -import com.android.internal.app.AlertActivity; -import com.android.internal.app.AlertController; import com.android.settings.R; import android.app.Activity; +import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; @@ -31,14 +30,12 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; -import android.view.View; -import android.widget.TextView; /** * RequestPermissionActivity asks the user whether to enable discovery. This is * usually started by an application wanted to start bluetooth and or discovery */ -public class RequestPermissionActivity extends AlertActivity implements +public class RequestPermissionActivity extends Activity implements DialogInterface.OnClickListener { // Command line to test this // adb shell am start -a android.bluetooth.adapter.action.REQUEST_ENABLE @@ -73,6 +70,8 @@ public class RequestPermissionActivity extends AlertActivity implements private boolean mUserConfirmed = false; + private AlertDialog mDialog = null; + private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -84,7 +83,7 @@ public class RequestPermissionActivity extends AlertActivity implements int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR); if (state == BluetoothAdapter.STATE_ON) { if (mUserConfirmed) { - proceedAndFinish(false); + proceedAndFinish(); } } } @@ -136,7 +135,7 @@ public class RequestPermissionActivity extends AlertActivity implements case BluetoothAdapter.STATE_ON: if (mEnableOnly) { // Nothing to do. Already enabled. - proceedAndFinish(false); + proceedAndFinish(); return; } else { // Ask the user about enabling discovery mode @@ -147,28 +146,24 @@ public class RequestPermissionActivity extends AlertActivity implements } private void createDialog() { - final AlertController.AlertParams p = mAlertParams; - p.mIconId = android.R.drawable.ic_dialog_info; - p.mTitle = getString(R.string.bluetooth_permission_request); - - View view = getLayoutInflater().inflate(R.layout.bluetooth_discoverable, null); - p.mView = view; - TextView tv = (TextView) view.findViewById(R.id.message); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setIcon(android.R.drawable.ic_dialog_info); + builder.setTitle(getString(R.string.bluetooth_permission_request)); if (mNeededToEnableBluetooth) { // RequestPermissionHelperActivity has gotten confirmation from user // to turn on BT - tv.setText(getString(R.string.bluetooth_turning_on)); + builder.setMessage(getString(R.string.bluetooth_turning_on)); + builder.setCancelable(false); } else { // Ask the user whether to turn on discovery mode or not - tv.setText(getString(R.string.bluetooth_ask_enablement_and_discovery, mTimeout)); - p.mPositiveButtonText = getString(R.string.yes); - p.mPositiveButtonListener = this; - p.mNegativeButtonText = getString(R.string.no); - p.mNegativeButtonListener = this; + builder.setMessage(getString(R.string.bluetooth_ask_enablement_and_discovery, mTimeout)); + builder.setPositiveButton(getString(R.string.yes), this); + builder.setNegativeButton(getString(R.string.no), this); } - setupAlert(); + mDialog = builder.create(); + mDialog.show(); } @Override @@ -182,6 +177,7 @@ public class RequestPermissionActivity extends AlertActivity implements if (resultCode != RESULT_BT_STARTING_OR_STARTED) { setResult(resultCode); finish(); + return; } // Back from RequestPermissionHelperActivity. User confirmed to enable @@ -189,7 +185,7 @@ public class RequestPermissionActivity extends AlertActivity implements mUserConfirmed = true; if (mLocalManager.getBluetoothState() == BluetoothAdapter.STATE_ON) { - proceedAndFinish(false); + proceedAndFinish(); } else { // If BT is not up yet, show "Turning on Bluetooth..." createDialog(); @@ -199,16 +195,17 @@ public class RequestPermissionActivity extends AlertActivity implements public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: - proceedAndFinish(true); + proceedAndFinish(); break; case DialogInterface.BUTTON_NEGATIVE: setResult(Activity.RESULT_CANCELED); + finish(); break; } } - private void proceedAndFinish(boolean buttonPressed) { + private void proceedAndFinish() { int returnCode; if (mEnableOnly) { @@ -227,10 +224,12 @@ public class RequestPermissionActivity extends AlertActivity implements returnCode = Activity.RESULT_CANCELED; } - setResult(returnCode); - if (!buttonPressed) { - finish(); + if (mDialog != null) { + mDialog.dismiss(); } + + setResult(returnCode); + finish(); } private boolean parseIntent() { diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java index 53e2a69..b574849 100644 --- a/src/com/android/settings/deviceinfo/Memory.java +++ b/src/com/android/settings/deviceinfo/Memory.java @@ -16,60 +16,86 @@ package com.android.settings.deviceinfo; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.content.DialogInterface.OnCancelListener; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; +import android.os.Message; import android.os.RemoteException; import android.os.Environment; -import android.os.IMountService; +import android.os.storage.IMountService; import android.os.ServiceManager; import android.os.StatFs; +import android.os.storage.StorageManager; +import android.os.storage.StorageEventListener; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.text.format.Formatter; import android.util.Log; +import android.widget.Toast; import com.android.settings.R; import java.io.File; -import java.text.DecimalFormat; +import java.util.HashSet; +import java.util.List; +import java.util.Set; -public class Memory extends PreferenceActivity { - +public class Memory extends PreferenceActivity implements OnCancelListener { private static final String TAG = "Memory"; + private static final boolean localLOGV = false; private static final String MEMORY_SD_SIZE = "memory_sd_size"; private static final String MEMORY_SD_AVAIL = "memory_sd_avail"; - private static final String MEMORY_SD_UNMOUNT = "memory_sd_unmount"; + private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle"; private static final String MEMORY_SD_FORMAT = "memory_sd_format"; + + private static final int DLG_CONFIRM_UNMOUNT = 1; + private static final int DLG_ERROR_UNMOUNT = 2; + private Resources mRes; private Preference mSdSize; private Preference mSdAvail; - private Preference mSdUnmount; + private Preference mSdMountToggle; private Preference mSdFormat; // Access using getMountService() private IMountService mMountService = null; + private StorageManager mStorageManager = null; + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - + + if (mStorageManager == null) { + mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); + mStorageManager.registerListener(mStorageListener); + } + addPreferencesFromResource(R.xml.device_info_memory); mRes = getResources(); mSdSize = findPreference(MEMORY_SD_SIZE); mSdAvail = findPreference(MEMORY_SD_AVAIL); - mSdUnmount = findPreference(MEMORY_SD_UNMOUNT); + mSdMountToggle = findPreference(MEMORY_SD_MOUNT_TOGGLE); mSdFormat = findPreference(MEMORY_SD_FORMAT); } @@ -77,27 +103,39 @@ public class Memory extends PreferenceActivity { protected void onResume() { super.onResume(); - IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_REMOVED); - intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); - intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED); - intentFilter.addAction(Intent.ACTION_MEDIA_SHARED); - intentFilter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); - intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE); - intentFilter.addAction(Intent.ACTION_MEDIA_NOFS); - intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED); intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); intentFilter.addDataScheme("file"); registerReceiver(mReceiver, intentFilter); updateMemoryStatus(); } + + StorageEventListener mStorageListener = new StorageEventListener() { + + @Override + public void onStorageStateChanged(String path, String oldState, String newState) { + Log.i(TAG, "Received storage state changed notification that " + + path + " changed state from " + oldState + + " to " + newState); + updateMemoryStatus(); + } + }; @Override protected void onPause() { super.onPause(); unregisterReceiver(mReceiver); } - + + @Override + protected void onDestroy() { + if (mStorageManager != null && mStorageListener != null) { + mStorageManager.unregisterListener(mStorageListener); + } + super.onDestroy(); + } + private synchronized IMountService getMountService() { if (mMountService == null) { IBinder service = ServiceManager.getService("mount"); @@ -112,8 +150,13 @@ public class Memory extends PreferenceActivity { @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - if (preference == mSdUnmount) { - unmount(); + if (preference == mSdMountToggle) { + String status = Environment.getExternalStorageState(); + if (status.equals(Environment.MEDIA_MOUNTED)) { + unmount(); + } else { + mount(); + } return true; } else if (preference == mSdFormat) { Intent intent = new Intent(Intent.ACTION_VIEW); @@ -132,17 +175,95 @@ public class Memory extends PreferenceActivity { } }; + @Override + public Dialog onCreateDialog(int id, Bundle args) { + switch (id) { + case DLG_CONFIRM_UNMOUNT: + return new AlertDialog.Builder(this) + .setTitle(R.string.dlg_confirm_unmount_title) + .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + doUnmount(true); + }}) + .setNegativeButton(R.string.cancel, null) + .setMessage(R.string.dlg_confirm_unmount_text) + .setOnCancelListener(this) + .create(); + case DLG_ERROR_UNMOUNT: + return new AlertDialog.Builder(this ) + .setTitle(R.string.dlg_error_unmount_title) + .setNeutralButton(R.string.dlg_ok, null) + .setMessage(R.string.dlg_error_unmount_text) + .setOnCancelListener(this) + .create(); + } + return null; + } + + private void doUnmount(boolean force) { + // Present a toast here + Toast.makeText(this, R.string.unmount_inform_text, Toast.LENGTH_SHORT).show(); + IMountService mountService = getMountService(); + String extStoragePath = Environment.getExternalStorageDirectory().toString(); + try { + mSdMountToggle.setEnabled(false); + mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title)); + mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary)); + mountService.unmountVolume(extStoragePath, force); + } catch (RemoteException e) { + // Informative dialog to user that + // unmount failed. + showDialogInner(DLG_ERROR_UNMOUNT); + } + } + + private void showDialogInner(int id) { + removeDialog(id); + showDialog(id); + } + + private boolean hasAppsAccessingStorage() throws RemoteException { + String extStoragePath = Environment.getExternalStorageDirectory().toString(); + IMountService mountService = getMountService(); + boolean showPidDialog = false; + int stUsers[] = mountService.getStorageUsers(extStoragePath); + if (stUsers != null && stUsers.length > 0) { + return true; + } + ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); + List<ApplicationInfo> list = am.getRunningExternalApplications(); + if (list != null && list.size() > 0) { + return true; + } + return false; + } + private void unmount() { + // Check if external media is in use. + try { + if (hasAppsAccessingStorage()) { + if (localLOGV) Log.i(TAG, "Do have storage users accessing media"); + // Present dialog to user + showDialogInner(DLG_CONFIRM_UNMOUNT); + } else { + doUnmount(true); + } + } catch (RemoteException e) { + // Very unlikely. But present an error dialog anyway + Log.e(TAG, "Is MountService running?"); + showDialogInner(DLG_ERROR_UNMOUNT); + } + } + + private void mount() { IMountService mountService = getMountService(); try { if (mountService != null) { - mountService.unmountMedia(Environment.getExternalStorageDirectory().toString()); + mountService.mountVolume(Environment.getExternalStorageDirectory().toString()); } else { - Log.e(TAG, "Mount service is null, can't unmount"); + Log.e(TAG, "Mount service is null, can't mount"); } } catch (RemoteException ex) { - // Failed for some reason, try to update UI to actual state - updateMemoryStatus(); } } @@ -166,7 +287,11 @@ public class Memory extends PreferenceActivity { mSdSize.setSummary(formatSize(totalBlocks * blockSize)); mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly); - mSdUnmount.setEnabled(true); + + mSdMountToggle.setEnabled(true); + mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject)); + mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary)); + } catch (IllegalArgumentException e) { // this can occur if the SD card is removed, but we haven't received the // ACTION_MEDIA_REMOVED Intent yet. @@ -176,15 +301,20 @@ public class Memory extends PreferenceActivity { } else { mSdSize.setSummary(mRes.getString(R.string.sd_unavailable)); mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable)); - mSdUnmount.setEnabled(false); + if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS) || status.equals(Environment.MEDIA_UNMOUNTABLE) ) { mSdFormat.setEnabled(true); + mSdMountToggle.setEnabled(true); + mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount)); + mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary)); + } else { + mSdMountToggle.setEnabled(false); + mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount)); + mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary)); } - - } File path = Environment.getDataDirectory(); @@ -197,5 +327,9 @@ public class Memory extends PreferenceActivity { private String formatSize(long size) { return Formatter.formatFileSize(this, size); } + + public void onCancel(DialogInterface dialog) { + finish(); + } } diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java index a736cc0..99a8975 100644 --- a/src/com/android/settings/deviceinfo/Status.java +++ b/src/com/android/settings/deviceinfo/Status.java @@ -32,6 +32,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.telephony.PhoneNumberUtils; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.TelephonyManager; @@ -221,7 +222,13 @@ public class Status extends PreferenceActivity { } } - setSummaryText("number", mPhone.getLine1Number()); + String rawNumber = mPhone.getLine1Number(); // may be null or empty + String formattedNumber = null; + if (!TextUtils.isEmpty(rawNumber)) { + formattedNumber = PhoneNumberUtils.formatNumber(rawNumber); + } + // If formattedNumber is null or empty, it'll display as "Unknown". + setSummaryText("number", formattedNumber); mPhoneStateReceiver = new PhoneStateIntentReceiver(this, mHandler); mPhoneStateReceiver.notifySignalStrength(EVENT_SIGNAL_STRENGTH_CHANGED); diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java index 737627a..4db968a 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java @@ -18,6 +18,9 @@ package com.android.settings.fuelgauge; import android.app.Activity; import android.app.ActivityManager; +import android.app.ApplicationErrorReport; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -26,7 +29,9 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; +import android.os.Process; import android.provider.Settings; import android.text.TextUtils; import android.view.LayoutInflater; @@ -35,8 +40,8 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; - import com.android.settings.InstalledAppDetails; +import com.android.settings.ManageApplications; import com.android.settings.R; public class PowerUsageDetail extends Activity implements Button.OnClickListener { @@ -68,6 +73,8 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener public static final int ACTION_WIRELESS_SETTINGS = 4; public static final int ACTION_APP_DETAILS = 5; public static final int ACTION_SECURITY_SETTINGS = 6; + public static final int ACTION_FORCE_STOP = 7; + public static final int ACTION_REPORT = 8; public static final int USAGE_SINCE_UNPLUGGED = 1; public static final int USAGE_SINCE_RESET = 2; @@ -78,6 +85,8 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener public static final String EXTRA_UID = "uid"; public static final String EXTRA_USAGE_SINCE = "since"; public static final String EXTRA_USAGE_DURATION = "duration"; + public static final String EXTRA_REPORT_DETAILS = "report_details"; + public static final String EXTRA_REPORT_CHECKIN_DETAILS = "report_checkin_details"; public static final String EXTRA_DETAIL_TYPES = "types"; // Array of usage types (cpu, gps, etc) public static final String EXTRA_DETAIL_VALUES = "values"; // Array of doubles public static final String EXTRA_DRAIN_TYPE = "drainType"; // DrainType @@ -92,6 +101,9 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener private int mUid; private double[] mValues; private TextView mTitleView; + private ViewGroup mTwoButtonsPanel; + private Button mForceStopButton; + private Button mReportButton; private ViewGroup mDetailsParent; private ViewGroup mControlsParent; private long mStartTime; @@ -105,6 +117,9 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener private static final String TAG = "PowerUsageDetail"; private String[] mPackages; + ApplicationInfo mApp; + ComponentName mInstaller; + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -116,6 +131,7 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener protected void onResume() { super.onResume(); mStartTime = android.os.Process.getElapsedCpuTime(); + checkForceStop(); } @Override @@ -163,6 +179,11 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener ((TextView)findViewById(R.id.battery_percentage)) .setText(String.format("%d%%", percentage)); + mTwoButtonsPanel = (ViewGroup) findViewById(R.id.two_buttons_panel); + mForceStopButton = (Button) findViewById(R.id.left_button); + mReportButton = (Button) findViewById(R.id.right_button); + mForceStopButton.setEnabled(false); + ImageView gaugeImage = (ImageView) findViewById(R.id.gauge); mGauge = new PercentageBar(); mGauge.percent = gaugeValue; @@ -178,6 +199,34 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener fillDetailsSection(); fillPackagesSection(mUid); fillControlsSection(mUid); + + if (mUid >= Process.FIRST_APPLICATION_UID) { + mForceStopButton.setText(R.string.force_stop); + mForceStopButton.setTag(ACTION_FORCE_STOP); + mForceStopButton.setOnClickListener(this); + mReportButton.setText(com.android.internal.R.string.report); + mReportButton.setTag(ACTION_REPORT); + mReportButton.setOnClickListener(this); + + // check if error reporting is enabled in secure settings + int enabled = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.SEND_ACTION_APP_ERROR, 0); + if (enabled != 0) { + if (mPackages != null && mPackages.length > 0) { + try { + mApp = getPackageManager().getApplicationInfo(mPackages[0], 0); + mInstaller = ApplicationErrorReport.getErrorReportReceiver( + this, mPackages[0], mApp.flags); + } catch (NameNotFoundException e) { + } + } + mReportButton.setEnabled(mInstaller != null); + } else { + mTwoButtonsPanel.setVisibility(View.GONE); + } + } else { + mTwoButtonsPanel.setVisibility(View.GONE); + } } public void onClick(View v) { @@ -201,12 +250,18 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener case ACTION_APP_DETAILS: Intent intent = new Intent(Intent.ACTION_VIEW); intent.setClass(this, InstalledAppDetails.class); - intent.putExtra("com.android.settings.ApplicationPkgName", mPackages[0]); + intent.putExtra(ManageApplications.APP_PKG_NAME, mPackages[0]); startActivity(intent); break; case ACTION_SECURITY_SETTINGS: startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS)); break; + case ACTION_FORCE_STOP: + killProcesses(); + break; + case ACTION_REPORT: + reportBatteryUse(); + break; } } @@ -271,7 +326,7 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener } break; case SCREEN: - addControl(R.string.sound_and_display_settings, + addControl(R.string.display_settings, R.string.battery_sugg_display, ACTION_DISPLAY_SETTINGS); removeHeader = false; @@ -330,10 +385,57 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener ActivityManager am = (ActivityManager)getSystemService( Context.ACTIVITY_SERVICE); for (int i = 0; i < mPackages.length; i++) { - am.restartPackage(mPackages[i]); + am.forceStopPackage(mPackages[i]); } + checkForceStop(); } + private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mForceStopButton.setEnabled(getResultCode() != RESULT_CANCELED); + } + }; + + private void checkForceStop() { + if (mPackages == null || mUid < Process.FIRST_APPLICATION_UID) { + mForceStopButton.setEnabled(false); + return; + } + Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, + Uri.fromParts("package", mPackages[0], null)); + intent.putExtra(Intent.EXTRA_PACKAGES, mPackages); + intent.putExtra(Intent.EXTRA_UID, mUid); + sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null, + Activity.RESULT_CANCELED, null, null); + } + + private void reportBatteryUse() { + if (mPackages == null) return; + + ApplicationErrorReport report = new ApplicationErrorReport(); + report.type = ApplicationErrorReport.TYPE_BATTERY; + report.packageName = mPackages[0]; + report.installerPackageName = mInstaller.getPackageName(); + report.processName = mPackages[0]; + report.time = System.currentTimeMillis(); + report.systemApp = (mApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + + final Intent intent = getIntent(); + ApplicationErrorReport.BatteryInfo batteryInfo = new ApplicationErrorReport.BatteryInfo(); + batteryInfo.usagePercent = intent.getIntExtra(EXTRA_PERCENT, 1); + batteryInfo.durationMicros = intent.getLongExtra(EXTRA_USAGE_DURATION, 0); + batteryInfo.usageDetails = intent.getStringExtra(EXTRA_REPORT_DETAILS); + batteryInfo.checkinDetails = intent.getStringExtra(EXTRA_REPORT_CHECKIN_DETAILS); + report.batteryInfo = batteryInfo; + + Intent result = new Intent(Intent.ACTION_APP_ERROR); + result.setComponent(mInstaller); + result.putExtra(Intent.EXTRA_BUG_REPORT, report); + result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(result); + } + private void fillPackagesSection(int uid) { if (uid < 1) { removePackagesSection(); @@ -344,7 +446,7 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener LayoutInflater inflater = getLayoutInflater(); PackageManager pm = getPackageManager(); - final Drawable defaultActivityIcon = pm.getDefaultActivityIcon(); + //final Drawable defaultActivityIcon = pm.getDefaultActivityIcon(); mPackages = pm.getPackagesForUid(uid); if (mPackages == null || mPackages.length < 2) { removePackagesSection(); @@ -356,13 +458,13 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener try { ApplicationInfo ai = pm.getApplicationInfo(mPackages[i], 0); CharSequence label = ai.loadLabel(pm); - Drawable icon = defaultActivityIcon; + //Drawable icon = defaultActivityIcon; if (label != null) { mPackages[i] = label.toString(); } - if (ai.icon != 0) { - icon = ai.loadIcon(pm); - } + //if (ai.icon != 0) { + // icon = ai.loadIcon(pm); + //} ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_package_item, null); packagesParent.addView(item); diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 10ab2d0..5678160 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -48,6 +48,9 @@ import com.android.internal.os.PowerProfile; import com.android.settings.R; import com.android.settings.fuelgauge.PowerUsageDetail.DrainType; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -78,6 +81,7 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable { private static final int MIN_POWER_THRESHOLD = 5; private static final int MAX_ITEMS_TO_LIST = 10; + private long mStatsPeriod = 0; private double mMaxPower = 1; private double mTotalPower; private PowerProfile mPowerProfile; @@ -132,6 +136,7 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable { Math.ceil(sipper.getSortValue() * 100 / mTotalPower)); intent.putExtra(PowerUsageDetail.EXTRA_GAUGE, (int) Math.ceil(sipper.getSortValue() * 100 / mMaxPower)); + intent.putExtra(PowerUsageDetail.EXTRA_USAGE_DURATION, mStatsPeriod); intent.putExtra(PowerUsageDetail.EXTRA_ICON_PACKAGE, sipper.defaultPackageName); intent.putExtra(PowerUsageDetail.EXTRA_ICON_ID, sipper.iconId); intent.putExtra(PowerUsageDetail.EXTRA_NO_COVERAGE, sipper.noCoveragePercent); @@ -165,6 +170,15 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable { 0 }; + Writer result = new StringWriter(); + PrintWriter printWriter = new PrintWriter(result); + mStats.dumpLocked(printWriter, "", mStatsType, uid.getUid()); + intent.putExtra(PowerUsageDetail.EXTRA_REPORT_DETAILS, result.toString()); + + result = new StringWriter(); + printWriter = new PrintWriter(result); + mStats.dumpCheckinLocked(printWriter, mStatsType, uid.getUid()); + intent.putExtra(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS, result.toString()); } break; case CELL: @@ -303,6 +317,7 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable { } final double averageCostPerByte = getAverageDataCost(); long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which); + mStatsPeriod = uSecTime; updateStatsPeriod(uSecTime); SparseArray<? extends Uid> uidStats = mStats.getUidStats(); final int NU = uidStats.size(); diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java index 5f6a207..7b8d433 100644 --- a/src/com/android/settings/vpn/VpnSettings.java +++ b/src/com/android/settings/vpn/VpnSettings.java @@ -82,7 +82,7 @@ public class VpnSettings extends PreferenceActivity implements private static final String PREF_ADD_VPN = "add_new_vpn"; private static final String PREF_VPN_LIST = "vpn_list"; - private static final String PROFILES_ROOT = VpnManager.PROFILES_PATH + "/"; + private static final String PROFILES_ROOT = VpnManager.getProfilePath() + "/"; private static final String PROFILE_OBJ_FILE = ".pobj"; private static final int REQUEST_ADD_OR_EDIT_PROFILE = 1; diff --git a/src/com/android/settings/widget/SettingsAppWidgetProvider.java b/src/com/android/settings/widget/SettingsAppWidgetProvider.java index 3db90cf..939e8e3 100644 --- a/src/com/android/settings/widget/SettingsAppWidgetProvider.java +++ b/src/com/android/settings/widget/SettingsAppWidgetProvider.java @@ -30,13 +30,13 @@ import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.Uri; import android.net.wifi.WifiManager; +import android.os.AsyncTask; import android.os.IPowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.util.Log; import android.widget.RemoteViews; -import android.widget.Toast; import com.android.settings.R; import com.android.settings.bluetooth.LocalBluetoothManager; @@ -50,7 +50,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { new ComponentName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); - private static LocalBluetoothManager mLocalBluetoothManager = null; + private static LocalBluetoothManager sLocalBluetoothManager = null; private static final int BUTTON_WIFI = 0; private static final int BUTTON_BRIGHTNESS = 1; @@ -58,9 +58,16 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { private static final int BUTTON_GPS = 3; private static final int BUTTON_BLUETOOTH = 4; + // This widget keeps track of two sets of states: + // "3-state": STATE_DISABLED, STATE_ENABLED, STATE_INTERMEDIATE + // "5-state": STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_UNKNOWN private static final int STATE_DISABLED = 0; private static final int STATE_ENABLED = 1; - private static final int STATE_INTERMEDIATE = 2; + private static final int STATE_TURNING_ON = 2; + private static final int STATE_TURNING_OFF = 3; + private static final int STATE_UNKNOWN = 4; + private static final int STATE_INTERMEDIATE = 5; + /** * Minimum and maximum brightnesses. Don't go to 0 since that makes the display unusable @@ -69,6 +76,299 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON; private static final int DEFAULT_BACKLIGHT = (int) (android.os.Power.BRIGHTNESS_ON * 0.4f); + private static final StateTracker sWifiState = new WifiStateTracker(); + private static final StateTracker sBluetoothState = new BluetoothStateTracker(); + + /** + * The state machine for Wifi and Bluetooth toggling, tracking + * reality versus the user's intent. + * + * This is necessary because reality moves relatively slowly + * (turning on & off radio drivers), compared to user's + * expectations. + */ + private abstract static class StateTracker { + // Is the state in the process of changing? + private boolean mInTransition = false; + private Boolean mActualState = null; // initially not set + private Boolean mIntendedState = null; // initially not set + + // Did a toggle request arrive while a state update was + // already in-flight? If so, the mIntendedState needs to be + // requested when the other one is done, unless we happened to + // arrive at that state already. + private boolean mDeferredStateChangeRequestNeeded = false; + + /** + * User pressed a button to change the state. Something + * should immediately appear to the user afterwards, even if + * we effectively do nothing. Their press must be heard. + */ + public final void toggleState(Context context) { + int currentState = getTriState(context); + boolean newState = false; + switch (currentState) { + case STATE_ENABLED: + newState = false; + break; + case STATE_DISABLED: + newState = true; + break; + case STATE_INTERMEDIATE: + if (mIntendedState != null) { + newState = !mIntendedState; + } + break; + } + mIntendedState = newState; + if (mInTransition) { + // We don't send off a transition request if we're + // already transitioning. Makes our state tracking + // easier, and is probably nicer on lower levels. + // (even though they should be able to take it...) + mDeferredStateChangeRequestNeeded = true; + } else { + mInTransition = true; + requestStateChange(context, newState); + } + } + + /** + * Update internal state from a broadcast state change. + */ + public abstract void onActualStateChange(Context context, Intent intent); + + /** + * Sets the value that we're now in. To be called from onActualStateChange. + * + * @param newState one of STATE_DISABLED, STATE_ENABLED, STATE_TURNING_ON, + * STATE_TURNING_OFF, STATE_UNKNOWN + */ + protected final void setCurrentState(Context context, int newState) { + final boolean wasInTransition = mInTransition; + switch (newState) { + case STATE_DISABLED: + mInTransition = false; + mActualState = false; + break; + case STATE_ENABLED: + mInTransition = false; + mActualState = true; + break; + case STATE_TURNING_ON: + mInTransition = true; + mActualState = false; + break; + case STATE_TURNING_OFF: + mInTransition = true; + mActualState = true; + break; + } + + if (wasInTransition && !mInTransition) { + if (mDeferredStateChangeRequestNeeded) { + Log.v(TAG, "processing deferred state change"); + if (mActualState != null && mIntendedState != null && + mIntendedState.equals(mActualState)) { + Log.v(TAG, "... but intended state matches, so no changes."); + } else if (mIntendedState != null) { + mInTransition = true; + requestStateChange(context, mIntendedState); + } + mDeferredStateChangeRequestNeeded = false; + } + } + } + + + /** + * If we're in a transition mode, this returns true if we're + * transitioning towards being enabled. + */ + public final boolean isTurningOn() { + return mIntendedState != null && mIntendedState; + } + + /** + * Returns simplified 3-state value from underlying 5-state. + * + * @param context + * @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE + */ + public final int getTriState(Context context) { + if (mInTransition) { + // If we know we just got a toggle request recently + // (which set mInTransition), don't even ask the + // underlying interface for its state. We know we're + // changing. This avoids blocking the UI thread + // during UI refresh post-toggle if the underlying + // service state accessor has coarse locking on its + // state (to be fixed separately). + return STATE_INTERMEDIATE; + } + switch (getActualState(context)) { + case STATE_DISABLED: + return STATE_DISABLED; + case STATE_ENABLED: + return STATE_ENABLED; + default: + return STATE_INTERMEDIATE; + } + } + + /** + * Gets underlying actual state. + * + * @param context + * @return STATE_ENABLED, STATE_DISABLED, STATE_ENABLING, STATE_DISABLING, + * or or STATE_UNKNOWN. + */ + public abstract int getActualState(Context context); + + /** + * Actually make the desired change to the underlying radio + * API. + */ + protected abstract void requestStateChange(Context context, boolean desiredState); + } + + /** + * Subclass of StateTracker to get/set Wifi state. + */ + private static final class WifiStateTracker extends StateTracker { + @Override + public int getActualState(Context context) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + if (wifiManager != null) { + return wifiStateToFiveState(wifiManager.getWifiState()); + } + return STATE_UNKNOWN; + } + + @Override + protected void requestStateChange(Context context, final boolean desiredState) { + final WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + if (wifiManager == null) { + Log.d(TAG, "No wifiManager."); + return; + } + + // Actually request the wifi change and persistent + // settings write off the UI thread, as it can take a + // user-noticeable amount of time, especially if there's + // disk contention. + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... args) { + /** + * Disable tethering if enabling Wifi + */ + int wifiApState = wifiManager.getWifiApState(); + if (desiredState && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || + (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { + wifiManager.setWifiApEnabled(null, false); + } + + wifiManager.setWifiEnabled(desiredState); + return null; + } + }.execute(); + } + + @Override + public void onActualStateChange(Context context, Intent intent) { + if (!WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { + return; + } + int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1); + setCurrentState(context, wifiStateToFiveState(wifiState)); + } + + /** + * Converts WifiManager's state values into our + * Wifi/Bluetooth-common state values. + */ + private static int wifiStateToFiveState(int wifiState) { + switch (wifiState) { + case WifiManager.WIFI_STATE_DISABLED: + return STATE_DISABLED; + case WifiManager.WIFI_STATE_ENABLED: + return STATE_ENABLED; + case WifiManager.WIFI_STATE_DISABLING: + return STATE_TURNING_OFF; + case WifiManager.WIFI_STATE_ENABLING: + return STATE_TURNING_ON; + default: + return STATE_UNKNOWN; + } + } + } + + /** + * Subclass of StateTracker to get/set Bluetooth state. + */ + private static final class BluetoothStateTracker extends StateTracker { + + @Override + public int getActualState(Context context) { + if (sLocalBluetoothManager == null) { + sLocalBluetoothManager = LocalBluetoothManager.getInstance(context); + if (sLocalBluetoothManager == null) { + return STATE_UNKNOWN; // On emulator? + } + } + return bluetoothStateToFiveState(sLocalBluetoothManager.getBluetoothState()); + } + + @Override + protected void requestStateChange(Context context, final boolean desiredState) { + if (sLocalBluetoothManager == null) { + Log.d(TAG, "No LocalBluetoothManager"); + return; + } + // Actually request the Bluetooth change and persistent + // settings write off the UI thread, as it can take a + // user-noticeable amount of time, especially if there's + // disk contention. + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... args) { + sLocalBluetoothManager.setBluetoothEnabled(desiredState); + return null; + } + }.execute(); + } + + @Override + public void onActualStateChange(Context context, Intent intent) { + if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int bluetoothState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); + setCurrentState(context, bluetoothStateToFiveState(bluetoothState)); + } + + /** + * Converts BluetoothAdapter's state values into our + * Wifi/Bluetooth-common state values. + */ + private static int bluetoothStateToFiveState(int bluetoothState) { + switch (bluetoothState) { + case BluetoothAdapter.STATE_OFF: + return STATE_DISABLED; + case BluetoothAdapter.STATE_ON: + return STATE_ENABLED; + case BluetoothAdapter.STATE_TURNING_ON: + return STATE_TURNING_ON; + case BluetoothAdapter.STATE_TURNING_OFF: + return STATE_TURNING_OFF; + default: + return STATE_UNKNOWN; + } + } + } + + @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { @@ -142,26 +442,53 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { * @param context */ private static void updateButtons(RemoteViews views, Context context) { - switch (getWifiState(context)) { + switch (sWifiState.getTriState(context)) { case STATE_DISABLED: - views.setImageViewResource(R.id.img_wifi, R.drawable.ic_appwidget_settings_wifi_off); - views.setImageViewResource(R.id.ind_wifi, R.drawable.appwidget_settings_ind_off_l); + views.setImageViewResource(R.id.img_wifi, + R.drawable.ic_appwidget_settings_wifi_off); + views.setImageViewResource(R.id.ind_wifi, + R.drawable.appwidget_settings_ind_off_l); break; case STATE_ENABLED: - views.setImageViewResource(R.id.img_wifi, R.drawable.ic_appwidget_settings_wifi_on); - views.setImageViewResource(R.id.ind_wifi, R.drawable.appwidget_settings_ind_on_l); + views.setImageViewResource(R.id.img_wifi, + R.drawable.ic_appwidget_settings_wifi_on); + views.setImageViewResource(R.id.ind_wifi, + R.drawable.appwidget_settings_ind_on_l); break; case STATE_INTERMEDIATE: - views.setImageViewResource(R.id.img_wifi, R.drawable.ic_appwidget_settings_wifi_off); - views.setImageViewResource(R.id.ind_wifi, R.drawable.appwidget_settings_ind_mid_l); + // In the transitional state, the bottom green bar + // shows the tri-state (on, off, transitioning), but + // the top dark-gray-or-bright-white logo shows the + // user's intent. This is much easier to see in + // sunlight. + if (sWifiState.isTurningOn()) { + views.setImageViewResource(R.id.img_wifi, + R.drawable.ic_appwidget_settings_wifi_on); + views.setImageViewResource(R.id.ind_wifi, + R.drawable.appwidget_settings_ind_mid_l); + } else { + views.setImageViewResource(R.id.img_wifi, + R.drawable.ic_appwidget_settings_wifi_off); + views.setImageViewResource(R.id.ind_wifi, + R.drawable.appwidget_settings_ind_off_l); + } break; } - if (getBrightness(context)) { - views.setImageViewResource(R.id.img_brightness, R.drawable.ic_appwidget_settings_brightness_on); - views.setImageViewResource(R.id.ind_brightness, R.drawable.appwidget_settings_ind_on_r); + if (getBrightnessMode(context)) { + views.setImageViewResource(R.id.img_brightness, + R.drawable.ic_appwidget_settings_brightness_auto); + views.setImageViewResource(R.id.ind_brightness, + R.drawable.appwidget_settings_ind_on_r); + } else if (getBrightness(context)) { + views.setImageViewResource(R.id.img_brightness, + R.drawable.ic_appwidget_settings_brightness_on); + views.setImageViewResource(R.id.ind_brightness, + R.drawable.appwidget_settings_ind_on_r); } else { - views.setImageViewResource(R.id.img_brightness, R.drawable.ic_appwidget_settings_brightness_off); - views.setImageViewResource(R.id.ind_brightness, R.drawable.appwidget_settings_ind_off_r); + views.setImageViewResource(R.id.img_brightness, + R.drawable.ic_appwidget_settings_brightness_off); + views.setImageViewResource(R.id.ind_brightness, + R.drawable.appwidget_settings_ind_off_r); } if (getSync(context)) { views.setImageViewResource(R.id.img_sync, R.drawable.ic_appwidget_settings_sync_on); @@ -177,18 +504,36 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { views.setImageViewResource(R.id.img_gps, R.drawable.ic_appwidget_settings_gps_off); views.setImageViewResource(R.id.ind_gps, R.drawable.appwidget_settings_ind_off_c); } - switch (getBluetoothState(context)) { + switch (sBluetoothState.getTriState(context)) { case STATE_DISABLED: - views.setImageViewResource(R.id.img_bluetooth, R.drawable.ic_appwidget_settings_bluetooth_off); - views.setImageViewResource(R.id.ind_bluetooth, R.drawable.appwidget_settings_ind_off_c); + views.setImageViewResource(R.id.img_bluetooth, + R.drawable.ic_appwidget_settings_bluetooth_off); + views.setImageViewResource(R.id.ind_bluetooth, + R.drawable.appwidget_settings_ind_off_c); break; case STATE_ENABLED: - views.setImageViewResource(R.id.img_bluetooth, R.drawable.ic_appwidget_settings_bluetooth_on); - views.setImageViewResource(R.id.ind_bluetooth, R.drawable.appwidget_settings_ind_on_c); + views.setImageViewResource(R.id.img_bluetooth, + R.drawable.ic_appwidget_settings_bluetooth_on); + views.setImageViewResource(R.id.ind_bluetooth, + R.drawable.appwidget_settings_ind_on_c); break; case STATE_INTERMEDIATE: - views.setImageViewResource(R.id.img_bluetooth, R.drawable.ic_appwidget_settings_bluetooth_off); - views.setImageViewResource(R.id.ind_bluetooth, R.drawable.appwidget_settings_ind_mid_c); + // In the transitional state, the bottom green bar + // shows the tri-state (on, off, transitioning), but + // the top dark-gray-or-bright-white logo shows the + // user's intent. This is much easier to see in + // sunlight. + if (sBluetoothState.isTurningOn()) { + views.setImageViewResource(R.id.img_bluetooth, + R.drawable.ic_appwidget_settings_bluetooth_on); + views.setImageViewResource(R.id.ind_bluetooth, + R.drawable.appwidget_settings_ind_mid_c); + } else { + views.setImageViewResource(R.id.img_bluetooth, + R.drawable.ic_appwidget_settings_bluetooth_off); + views.setImageViewResource(R.id.ind_bluetooth, + R.drawable.appwidget_settings_ind_off_c); + } break; } } @@ -220,11 +565,15 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); - if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { + sWifiState.onActualStateChange(context, intent); + } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { + sBluetoothState.onActualStateChange(context, intent); + } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { Uri data = intent.getData(); int buttonId = Integer.parseInt(data.getSchemeSpecificPart()); if (buttonId == BUTTON_WIFI) { - toggleWifi(context); + sWifiState.toggleState(context); } else if (buttonId == BUTTON_BRIGHTNESS) { toggleBrightness(context); } else if (buttonId == BUTTON_SYNC) { @@ -232,45 +581,17 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { } else if (buttonId == BUTTON_GPS) { toggleGps(context); } else if (buttonId == BUTTON_BLUETOOTH) { - toggleBluetooth(context); + sBluetoothState.toggleState(context); } - } - // State changes fall through - updateWidget(context); - } - - /** - * Gets the state of Wi-Fi - * - * @param context - * @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE - */ - private static int getWifiState(Context context) { - WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - int wifiState = wifiManager.getWifiState(); - if (wifiState == WifiManager.WIFI_STATE_DISABLED) { - return STATE_DISABLED; - } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) { - return STATE_ENABLED; } else { - return STATE_INTERMEDIATE; + // Don't fall-through to updating the widget. The Intent + // was something unrelated or that our super class took + // care of. + return; } - } - /** - * Toggles the state of Wi-Fi - * - * @param context - */ - private void toggleWifi(Context context) { - WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - int wifiState = getWifiState(context); - if (wifiState == STATE_ENABLED) { - wifiManager.setWifiEnabled(false); - } else if (wifiState == STATE_DISABLED) { - wifiManager.setWifiEnabled(true); - } - Toast.makeText(context, R.string.gadget_toggle_wifi, Toast.LENGTH_SHORT).show(); + // State changes fall through + updateWidget(context); } /** @@ -378,6 +699,27 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { } /** + * Gets state of brightness mode. + * + * @param context + * @return true if auto brightness is on. + */ + private static boolean getBrightnessMode(Context context) { + try { + IPowerManager power = IPowerManager.Stub.asInterface( + ServiceManager.getService("power")); + if (power != null) { + int brightnessMode = Settings.System.getInt(context.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE); + return brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + } + } catch (Exception e) { + Log.d(TAG, "getBrightnessMode: " + e); + } + return false; + } + + /** * Increases or decreases the brightness. * * @param context @@ -390,25 +732,41 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { ContentResolver cr = context.getContentResolver(); int brightness = Settings.System.getInt(cr, Settings.System.SCREEN_BRIGHTNESS); - // Rotate MINIMUM -> DEFAULT -> MAXIMUM + int brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; + //Only get brightness setting if available + if (context.getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available)) { + brightnessMode = Settings.System.getInt(cr, + Settings.System.SCREEN_BRIGHTNESS_MODE); + } + + // Rotate AUTO -> MINIMUM -> DEFAULT -> MAXIMUM // Technically, not a toggle... - if (brightness < DEFAULT_BACKLIGHT) { + if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { + brightness = MINIMUM_BACKLIGHT; + brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; + } else if (brightness < DEFAULT_BACKLIGHT) { brightness = DEFAULT_BACKLIGHT; } else if (brightness < MAXIMUM_BACKLIGHT) { brightness = MAXIMUM_BACKLIGHT; } else { + brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; brightness = MINIMUM_BACKLIGHT; } - power.setBacklightBrightness(brightness); - Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness); + if (context.getResources().getBoolean( com.android.internal.R.bool.config_automatic_brightness_available)) { - // Disable automatic brightness + // Set screen brightness mode (automatic or manual) Settings.System.putInt(context.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, - Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); - // Set it again in case auto brightness was on + brightnessMode); + } else { + // Make sure we set the brightness if automatic mode isn't available + brightnessMode = Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL; + } + if (brightnessMode == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) { power.setBacklightBrightness(brightness); + Settings.System.putInt(cr, Settings.System.SCREEN_BRIGHTNESS, brightness); } } } catch (RemoteException e) { @@ -417,42 +775,4 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { Log.d(TAG, "toggleBrightness: " + e); } } - - /** - * Gets state of bluetooth - * - * @param context - * @return STATE_ENABLED, STATE_DISABLED, or STATE_INTERMEDIATE - */ - private static int getBluetoothState(Context context) { - if (mLocalBluetoothManager == null) { - mLocalBluetoothManager = LocalBluetoothManager.getInstance(context); - if (mLocalBluetoothManager == null) { - return STATE_INTERMEDIATE; // On emulator? - } - } - int state = mLocalBluetoothManager.getBluetoothState(); - if (state == BluetoothAdapter.STATE_OFF) { - return STATE_DISABLED; - } else if (state == BluetoothAdapter.STATE_ON) { - return STATE_ENABLED; - } else { - return STATE_INTERMEDIATE; - } - } - - /** - * Toggles the state of bluetooth - * - * @param context - */ - private void toggleBluetooth(Context context) { - int state = getBluetoothState(context); - if (state == STATE_ENABLED) { - mLocalBluetoothManager.setBluetoothEnabled(false); - } else if (state == STATE_DISABLED) { - mLocalBluetoothManager.setBluetoothEnabled(true); - } - Toast.makeText(context, R.string.gadget_toggle_bluetooth, Toast.LENGTH_SHORT).show(); - } } diff --git a/src/com/android/settings/wifi/AccessPoint.java b/src/com/android/settings/wifi/AccessPoint.java new file mode 100644 index 0000000..141c412 --- /dev/null +++ b/src/com/android/settings/wifi/AccessPoint.java @@ -0,0 +1,225 @@ +/* + * 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.wifi; + +import com.android.settings.R; + +import android.content.Context; +import android.net.NetworkInfo.DetailedState; +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.ScanResult; +import android.preference.Preference; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; + +class AccessPoint extends Preference { + private static final int[] STATE_SECURED = {R.attr.state_encrypted}; + private static final int[] STATE_NONE = {}; + + static final int SECURITY_NONE = 0; + static final int SECURITY_WEP = 1; + static final int SECURITY_PSK = 2; + static final int SECURITY_EAP = 3; + + final String ssid; + final int security; + final int networkId; + + private WifiConfiguration mConfig; + private int mRssi; + private WifiInfo mInfo; + private DetailedState mState; + private ImageView mSignal; + + static int getSecurity(WifiConfiguration config) { + if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + return SECURITY_PSK; + } + if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) || + config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { + return SECURITY_EAP; + } + return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE; + } + + private static int getSecurity(ScanResult result) { + if (result.capabilities.contains("WEP")) { + return SECURITY_WEP; + } else if (result.capabilities.contains("PSK")) { + return SECURITY_PSK; + } else if (result.capabilities.contains("EAP")) { + return SECURITY_EAP; + } + return SECURITY_NONE; + } + + AccessPoint(Context context, WifiConfiguration config) { + super(context); + setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); + ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); + security = getSecurity(config); + networkId = config.networkId; + mConfig = config; + mRssi = Integer.MAX_VALUE; + } + + AccessPoint(Context context, ScanResult result) { + super(context); + setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); + ssid = result.SSID; + security = getSecurity(result); + networkId = -1; + mRssi = result.level; + } + + @Override + protected void onBindView(View view) { + setTitle(ssid); + mSignal = (ImageView) view.findViewById(R.id.signal); + if (mRssi == Integer.MAX_VALUE) { + mSignal.setImageDrawable(null); + } else { + mSignal.setImageResource(R.drawable.wifi_signal); + mSignal.setImageState((security != SECURITY_NONE) ? + STATE_SECURED : STATE_NONE, true); + } + refresh(); + super.onBindView(view); + } + + @Override + public int compareTo(Preference preference) { + if (!(preference instanceof AccessPoint)) { + return 1; + } + AccessPoint other = (AccessPoint) preference; + // Active one goes first. + if (mInfo != other.mInfo) { + return (mInfo != null) ? -1 : 1; + } + // Reachable one goes before unreachable one. + if ((mRssi ^ other.mRssi) < 0) { + return (mRssi != Integer.MAX_VALUE) ? -1 : 1; + } + // Configured one goes before unconfigured one. + if ((networkId ^ other.networkId) < 0) { + return (networkId != -1) ? -1 : 1; + } + // Sort by signal strength. + int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi); + if (difference != 0) { + return difference; + } + // Sort by ssid. + return ssid.compareToIgnoreCase(other.ssid); + } + + boolean update(ScanResult result) { + // We do not call refresh() since this is called before onBindView(). + if (ssid.equals(result.SSID) && security == getSecurity(result)) { + if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) { + mRssi = result.level; + } + return true; + } + return false; + } + + void update(WifiInfo info, DetailedState state) { + boolean reorder = false; + if (info != null && networkId != -1 && networkId == info.getNetworkId()) { + reorder = (mInfo == null); + mRssi = info.getRssi(); + mInfo = info; + mState = state; + refresh(); + } else if (mInfo != null) { + reorder = true; + mInfo = null; + mState = null; + refresh(); + } + if (reorder) { + notifyHierarchyChanged(); + } + } + + int getLevel() { + if (mRssi == Integer.MAX_VALUE) { + return -1; + } + return WifiManager.calculateSignalLevel(mRssi, 4); + } + + WifiConfiguration getConfig() { + return mConfig; + } + + WifiInfo getInfo() { + return mInfo; + } + + DetailedState getState() { + return mState; + } + + static String removeDoubleQuotes(String string) { + int length = string.length(); + if ((length > 1) && (string.charAt(0) == '"') + && (string.charAt(length - 1) == '"')) { + return string.substring(1, length - 1); + } + return string; + } + + static String convertToQuotedString(String string) { + return "\"" + string + "\""; + } + + private void refresh() { + if (mSignal == null) { + return; + } + Context context = getContext(); + mSignal.setImageLevel(getLevel()); + + if (mState != null) { + setSummary(Summary.get(context, mState)); + } else { + String status = null; + if (mRssi == Integer.MAX_VALUE) { + status = context.getString(R.string.wifi_not_in_range); + } else if (mConfig != null) { + status = context.getString((mConfig.status == WifiConfiguration.Status.DISABLED) ? + R.string.wifi_disabled : R.string.wifi_remembered); + } + + if (security == SECURITY_NONE) { + setSummary(status); + } else { + String format = context.getString((status == null) ? + R.string.wifi_secured : R.string.wifi_secured_with_status); + String[] type = context.getResources().getStringArray(R.array.wifi_security); + setSummary(String.format(format, type[security], status)); + } + } + } +} diff --git a/src/com/android/settings/wifi/AccessPointDialog.java b/src/com/android/settings/wifi/AccessPointDialog.java deleted file mode 100644 index 4804d78..0000000 --- a/src/com/android/settings/wifi/AccessPointDialog.java +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.settings.R; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; -import android.security.Credentials; -import android.security.KeyStore; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.os.Bundle; -import android.text.InputType; -import android.text.TextUtils; -import android.text.format.Formatter; -import android.text.method.PasswordTransformationMethod; -import android.text.method.TransformationMethod; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TableLayout; -import android.widget.TextView; - -public class AccessPointDialog extends AlertDialog implements DialogInterface.OnClickListener, - AdapterView.OnItemSelectedListener, View.OnClickListener { - - private static final String TAG = "AccessPointDialog"; - private static final String INSTANCE_KEY_ACCESS_POINT_STATE = - "com.android.settings.wifi.AccessPointDialog:accessPointState"; - private static final String INSTANCE_KEY_MODE = - "com.android.settings.wifi.AccessPointDialog:mode"; - private static final String INSTANCE_KEY_CUSTOM_TITLE = - "com.android.settings.wifi.AccessPointDialog:customTitle"; - private static final String INSTANCE_KEY_AUTO_SECURITY_ALLOWED = - "com.android.settings.wifi.AccessPointDialog:autoSecurityAllowed"; - - private static final int POSITIVE_BUTTON = BUTTON1; - private static final int NEGATIVE_BUTTON = BUTTON2; - private static final int NEUTRAL_BUTTON = BUTTON3; - - /** The dialog should show info connectivity functionality */ - public static final int MODE_INFO = 0; - /** The dialog should configure the detailed AP properties */ - public static final int MODE_CONFIGURE = 1; - /** The dialog should have the password field and connect/cancel */ - public static final int MODE_RETRY_PASSWORD = 2; - - // These should be matched with the XML. Both arrays in XML depend on this - // ordering! - private static final int SECURITY_AUTO = 0; - private static final int SECURITY_NONE = 1; - private static final int SECURITY_WEP = 2; - private static final int SECURITY_PSK = 3; - private static final int SECURITY_EAP = 4; - - private static final int[] WEP_TYPE_VALUES = { - AccessPointState.WEP_PASSWORD_AUTO, AccessPointState.WEP_PASSWORD_ASCII, - AccessPointState.WEP_PASSWORD_HEX - }; - private static final String NOT_APPLICABLE = "N/A"; - private static final String KEYSTORE_HEADER = "keystore://"; - - // Button positions, default to impossible values - private int mConnectButtonPos = Integer.MAX_VALUE; - private int mForgetButtonPos = Integer.MAX_VALUE; - private int mSaveButtonPos = Integer.MAX_VALUE; - - // Client configurable items. Generally, these should be saved in instance state - private int mMode = MODE_INFO; - private boolean mAutoSecurityAllowed = true; - private CharSequence mCustomTitle; - // This does not need to be saved in instance state. - private WifiLayer mWifiLayer; - private AccessPointState mState; - - // General views - private View mView; - private View mEnterpriseView; - private TextView mPasswordText; - private EditText mPasswordEdit; - private CheckBox mShowPasswordCheckBox; - - // Enterprise fields - private TextView mEapText; - private Spinner mEapSpinner; - private TextView mPhase2Text; - private Spinner mPhase2Spinner; - private TextView mIdentityText; - private EditText mIdentityEdit; - private TextView mAnonymousIdentityText; - private EditText mAnonymousIdentityEdit; - private TextView mCaCertText; - private Spinner mCaCertSpinner; - private TextView mClientCertText; - private Spinner mClientCertSpinner; - private EditText[] mEnterpriseTextFields; - - - // Info-specific views - private ViewGroup mTable; - - // Configure-specific views - private EditText mSsidEdit; - private TextView mSsidText; - private TextView mSecurityText; - private Spinner mSecuritySpinner; - private Spinner mWepTypeSpinner; - private KeyStore mKeyStore; - - public AccessPointDialog(Context context, WifiLayer wifiLayer) { - super(context); - - mWifiLayer = wifiLayer; - mKeyStore = KeyStore.getInstance(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - onLayout(); - onFill(); - - super.onCreate(savedInstanceState); - } - - @Override - public void onRestoreInstanceState(Bundle savedInstanceState) { - // Set to a class loader that can find AccessPointState - savedInstanceState.setClassLoader(getClass().getClassLoader()); - - mState = savedInstanceState.getParcelable(INSTANCE_KEY_ACCESS_POINT_STATE); - mState.setContext(getContext()); - - mMode = savedInstanceState.getInt(INSTANCE_KEY_MODE, mMode); - mAutoSecurityAllowed = savedInstanceState.getBoolean(INSTANCE_KEY_AUTO_SECURITY_ALLOWED, - mAutoSecurityAllowed); - mCustomTitle = savedInstanceState.getCharSequence(INSTANCE_KEY_CUSTOM_TITLE); - if (mCustomTitle != null) { - setTitle(mCustomTitle); - } - - // This is called last since it depends on the above values - super.onRestoreInstanceState(savedInstanceState); - - if (mShowPasswordCheckBox != null) { - // Restore the show-password-state on the edit text - setShowPassword(mShowPasswordCheckBox.isChecked()); - } - } - - @Override - public Bundle onSaveInstanceState() { - Bundle bundle = super.onSaveInstanceState(); - bundle.putParcelable(INSTANCE_KEY_ACCESS_POINT_STATE, mState); - bundle.putInt(INSTANCE_KEY_MODE, mMode); - bundle.putBoolean(INSTANCE_KEY_AUTO_SECURITY_ALLOWED, mAutoSecurityAllowed); - bundle.putCharSequence(INSTANCE_KEY_CUSTOM_TITLE, mCustomTitle); - return bundle; - } - - /** - * Sets state to show in this dialog. - * - * @param state The state. - */ - public void setState(AccessPointState state) { - mState = state; - } - - /** - * Sets the dialog mode. - * @param mode One of {@link #MODE_CONFIGURE} or {@link #MODE_INFO} - */ - public void setMode(int mode) { - mMode = mode; - } - - public void setAutoSecurityAllowed(boolean autoSecurityAllowed) { - mAutoSecurityAllowed = autoSecurityAllowed; - } - - @Override - public void setTitle(CharSequence title) { - super.setTitle(title); - mCustomTitle = title; - } - - @Override - public void setTitle(int titleId) { - setTitle(getContext().getString(titleId)); - } - - public void enableEnterpriseFields() { - setEnterpriseFieldsVisible(true); - updateCertificateSelection(); - setGenericPasswordVisible(true); - // Both WPA and WPA2 show the same caption, so either is ok - updatePasswordCaption(AccessPointState.PSK); - } - - /** Called after flags are set, the dialog's layout/etc should be set up here */ - private void onLayout() { - final Context context = getContext(); - final String ssid = mState.getHumanReadableSsid(); - - int positiveButtonResId = 0; - int negativeButtonResId = R.string.cancel; - int neutralButtonResId = 0; - - if (mCustomTitle == null) { - // Generic title is the SSID - // We don't want to trigger this as a custom title, so call super's - super.setTitle(ssid); - } - setInverseBackgroundForced(true); - - boolean defaultPasswordVisibility = true; - - if (mMode == MODE_CONFIGURE) { - setLayout(R.layout.wifi_ap_configure); - - positiveButtonResId = R.string.wifi_save_config; - mSaveButtonPos = POSITIVE_BUTTON; - - setEnterpriseFieldsVisible(false); - - } else if (mMode == MODE_INFO) { - if (mState.isEnterprise() && !mState.configured) { - setLayout(R.layout.wifi_ap_configure); - setEnterpriseFieldsVisible(true); - } else { - setLayout(R.layout.wifi_ap_info); - } - - if (mState.isConnectable()) { - if (mCustomTitle == null) { - // We don't want to trigger this as a custom title, so call super's - super.setTitle(context.getString(R.string.connect_to_blank, ssid)); - } - positiveButtonResId = R.string.connect; - mConnectButtonPos = POSITIVE_BUTTON; - } - - if (mState.isForgetable()) { - if (positiveButtonResId == 0) { - positiveButtonResId = R.string.forget_network; - mForgetButtonPos = POSITIVE_BUTTON; - } else { - neutralButtonResId = R.string.forget_network; - mForgetButtonPos = NEUTRAL_BUTTON; - } - } - } else if (mMode == MODE_RETRY_PASSWORD) { - setLayout(R.layout.wifi_ap_retry_password); - - positiveButtonResId = R.string.connect; - mConnectButtonPos = POSITIVE_BUTTON; - - setGenericPasswordVisible(true); - defaultPasswordVisibility = false; - } - - if (defaultPasswordVisibility) { - if (!mState.configured && mState.seen && mState.hasSecurity()) { - setGenericPasswordVisible(true); - } else { - setGenericPasswordVisible(false); - } - } - - setButtons(positiveButtonResId, negativeButtonResId, neutralButtonResId); - } - - /** Called when we need to set our member variables to point to the views. */ - private void onReferenceViews(View view) { - mPasswordText = (TextView) view.findViewById(R.id.password_text); - mPasswordEdit = (EditText) view.findViewById(R.id.password_edit); - mSsidText = (TextView) view.findViewById(R.id.ssid_text); - mSsidEdit = (EditText) view.findViewById(R.id.ssid_edit); - mSecurityText = (TextView) view.findViewById(R.id.security_text); - mSecuritySpinner = (Spinner) view.findViewById(R.id.security_spinner); - mWepTypeSpinner = (Spinner) view.findViewById(R.id.wep_type_spinner); - mEnterpriseView = mView.findViewById(R.id.enterprise_wrapper); - - mShowPasswordCheckBox = (CheckBox) view.findViewById(R.id.show_password_checkbox); - if (mShowPasswordCheckBox != null) { - mShowPasswordCheckBox.setOnClickListener(this); - } - if (mMode == MODE_CONFIGURE) { - mSecuritySpinner.setOnItemSelectedListener(this); - mSecuritySpinner.setPromptId(R.string.security); - setSpinnerAdapter(mSecuritySpinner, mAutoSecurityAllowed ? - R.array.wifi_security_entries - : R.array.wifi_security_without_auto_entries); - } else if (mMode == MODE_INFO) { - mTable = (ViewGroup) view.findViewById(R.id.table); - } - /* for enterprise one */ - if (mMode == MODE_CONFIGURE || - (mState.isEnterprise() && !mState.configured)) { - setEnterpriseFields(view); - updateCertificateSelection(); - } - } - - private void updateCertificateSelection() { - setSpinnerAdapter(mClientCertSpinner, getAllUserCertificateKeys()); - setSpinnerAdapter(mCaCertSpinner, getAllCaCertificateKeys()); - - mPhase2Spinner.setSelection(getSelectionIndex( - R.array.wifi_phase2_entries, mState.getPhase2())); - mEapSpinner.setSelection(getSelectionIndex( - R.array.wifi_eap_entries, mState.getEap())); - mClientCertSpinner.setSelection(getSelectionIndex( - getAllUserCertificateKeys(), mState.getEnterpriseField( - AccessPointState.CLIENT_CERT))); - mCaCertSpinner.setSelection(getSelectionIndex( - getAllCaCertificateKeys(), mState.getEnterpriseField( - AccessPointState.CA_CERT))); - } - - private String[] getAllCaCertificateKeys() { - return appendEmptyInSelection(mKeyStore.saw(Credentials.CA_CERTIFICATE)); - } - - private String[] getAllUserCertificateKeys() { - return appendEmptyInSelection(mKeyStore.saw(Credentials.USER_CERTIFICATE)); - } - - private String[] appendEmptyInSelection(String[] keys) { - if (keys == null) { - return new String[] {NOT_APPLICABLE}; - } else { - String[] selections = new String[keys.length + 1]; - System.arraycopy(keys, 0, selections, 0, keys.length); - selections[keys.length] = NOT_APPLICABLE; - return selections; - } - } - - private void setEnterpriseFields(View view) { - mIdentityText = (TextView) view.findViewById(R.id.identity_text); - mIdentityEdit = (EditText) view.findViewById(R.id.identity_edit); - mAnonymousIdentityText = - (TextView) view.findViewById(R.id.anonymous_identity_text); - mAnonymousIdentityEdit = - (EditText) view.findViewById(R.id.anonymous_identity_edit); - mClientCertText = - (TextView) view.findViewById(R.id.client_certificate_text); - mCaCertText = (TextView) view.findViewById(R.id.ca_certificate_text); - mEapText = (TextView) view.findViewById(R.id.eap_text); - mEapSpinner = (Spinner) view.findViewById(R.id.eap_spinner); - mEapSpinner.setOnItemSelectedListener(this); - mEapSpinner.setPromptId(R.string.please_select_eap); - setSpinnerAdapter(mEapSpinner, R.array.wifi_eap_entries); - - mPhase2Text = (TextView) view.findViewById(R.id.phase2_text); - mPhase2Spinner = (Spinner) view.findViewById(R.id.phase2_spinner); - mPhase2Spinner.setOnItemSelectedListener(this); - mPhase2Spinner.setPromptId(R.string.please_select_phase2); - setSpinnerAdapter(mPhase2Spinner, R.array.wifi_phase2_entries); - - mClientCertSpinner = - (Spinner) view.findViewById(R.id.client_certificate_spinner); - mClientCertSpinner.setOnItemSelectedListener(this); - mClientCertSpinner.setPromptId( - R.string.please_select_client_certificate); - setSpinnerAdapter(mClientCertSpinner, getAllUserCertificateKeys()); - - mCaCertSpinner = - (Spinner) view.findViewById(R.id.ca_certificate_spinner); - mCaCertSpinner.setOnItemSelectedListener(this); - mCaCertSpinner.setPromptId(R.string.please_select_ca_certificate); - setSpinnerAdapter(mCaCertSpinner, getAllCaCertificateKeys()); - - mEnterpriseTextFields = new EditText[] { - mIdentityEdit, mAnonymousIdentityEdit - }; - - } - - private void setSpinnerAdapter(Spinner spinner, String[] items) { - if (items != null) { - ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>( - getContext(), android.R.layout.simple_spinner_item, items); - adapter.setDropDownViewResource( - android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(adapter); - } - } - - private void setSpinnerAdapter(Spinner spinner, int arrayResId) { - setSpinnerAdapter(spinner, - getContext().getResources().getStringArray(arrayResId)); - } - - /** Called when the widgets are in-place waiting to be filled with data */ - private void onFill() { - - // Appears in the order added - if (mMode == MODE_INFO) { - if (mState.primary) { - addInfoRow(R.string.wifi_status, mState.getSummarizedStatus()); - addInfoRow(R.string.wifi_link_speed, mState.linkSpeed + WifiInfo.LINK_SPEED_UNITS); - } - - if (mState.seen) { - addInfoRow(R.string.signal, getSignalResId(mState.signal)); - } - - if (mState.security != null) { - addInfoRow(R.string.security, mState.getHumanReadableSecurity()); - } - - if (mState.primary && mState.ipAddress != 0) { - addInfoRow(R.string.ip_address, Formatter.formatIpAddress(mState.ipAddress)); - } - - } else if (mMode == MODE_CONFIGURE) { - String ssid = mState.getHumanReadableSsid(); - if (!TextUtils.isEmpty(ssid)) { - mSsidEdit.setText(ssid); - } - if (mState.configured) { - mPasswordEdit.setHint(R.string.wifi_password_unchanged); - } - } - - updatePasswordCaption(mState.security); - } - - private void updatePasswordCaption(String security) { - if (mPasswordText != null) { - if (security != null && security.equals(AccessPointState.WEP)) { - mPasswordText.setText(R.string.please_type_hex_key); - } else { - mPasswordText.setText(R.string.please_type_passphrase); - } - } - } - - private void addInfoRow(int nameResId, String value) { - View rowView = getLayoutInflater().inflate(R.layout.wifi_ap_info_row, mTable, false); - ((TextView) rowView.findViewById(R.id.name)).setText(nameResId); - ((TextView) rowView.findViewById(R.id.value)).setText(value); - mTable.addView(rowView); - } - - private void addInfoRow(int nameResId, int valueResId) { - addInfoRow(nameResId, getContext().getString(valueResId)); - } - - private void setButtons(int positiveResId, int negativeResId, int neutralResId) { - final Context context = getContext(); - - if (positiveResId > 0) { - setButton(context.getString(positiveResId), this); - } - - if (negativeResId > 0) { - setButton2(context.getString(negativeResId), this); - } - - if (neutralResId > 0) { - setButton3(context.getString(neutralResId), this); - } - } - - private void setLayout(int layoutResId) { - setView(mView = getLayoutInflater().inflate(layoutResId, null)); - onReferenceViews(mView); - } - - public void onClick(DialogInterface dialog, int which) { - if (which == mForgetButtonPos) { - handleForget(); - } else if (which == mConnectButtonPos) { - handleConnect(); - } else if (which == mSaveButtonPos) { - handleSave(); - } - } - - private void handleForget() { - if (!replaceStateWithWifiLayerInstance()) return; - mWifiLayer.forgetNetwork(mState); - } - - private void handleConnect() { - if (!replaceStateWithWifiLayerInstance()) { - Log.w(TAG, "Assuming connecting to a new network."); - } - - if (mState.isEnterprise()) { - if(!mState.configured) { - updateEnterpriseFields(); - } - } - updatePasswordField(); - - mWifiLayer.connectToNetwork(mState); - } - - /* - * If the network is secured and they haven't entered a password, popup an - * error. Allow empty passwords if the state already has a password set - * (since in that scenario, an empty password means keep the old password). - */ - private void updatePasswordField() { - - String password = getEnteredPassword(); - boolean passwordIsEmpty = TextUtils.isEmpty(password); - /* - * When 'retry password', they can not enter a blank password. In any - * other mode, we let them enter a blank password if the state already - * has a password. - */ - if (passwordIsEmpty && (!mState.hasPassword() || - mMode == MODE_RETRY_PASSWORD) && - (mState.security != null) && - !mState.security.equals(AccessPointState.OPEN) && - !mState.isEnterprise()) { - new AlertDialog.Builder(getContext()) - .setTitle(R.string.error_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setMessage(R.string.wifi_password_incorrect_error) - .setPositiveButton(android.R.string.ok, null) - .show(); - return; - } - - if (!passwordIsEmpty) { - mState.setPassword(password); - } - } - - private void handleSave() { - replaceStateWithWifiLayerInstance(); - - String ssid = mSsidEdit.getText().toString(); - String password = mPasswordEdit.getText().toString(); - - mState.setSsid(ssid); - - int securityType = getSecurityTypeFromSpinner(); - - if (!TextUtils.isEmpty(password) && (securityType != SECURITY_WEP)) { - mState.setPassword(password); - } - - switch (securityType) { - case SECURITY_PSK: { - mState.setSecurity(AccessPointState.PSK); - break; - } - - case SECURITY_AUTO: { - break; - } - - case SECURITY_WEP: { - mState.setSecurity(AccessPointState.WEP); - mState.setPassword(password, WEP_TYPE_VALUES[ - mWepTypeSpinner.getSelectedItemPosition()]); - break; - } - - case SECURITY_EAP: - mState.setSecurity(AccessPointState.EAP); - break; - - case SECURITY_NONE: - default: - mState.setSecurity(AccessPointState.OPEN); - break; - } - - if (mState.isEnterprise() && !mState.configured) { - updateEnterpriseFields(); - } - - if (!mWifiLayer.saveNetwork(mState)) { - return; - } - - // Connect right away if they've touched it - if (!mWifiLayer.connectToNetwork(mState)) { - return; - } - - } - - private int getSelectionIndex(String[] array, String selection) { - if(selection != null) { - for (int i = 0 ; i < array.length ; i++) { - if (selection.contains(array[i])) return i; - } - } - return 0; - } - - private int getSelectionIndex(int arrayResId, String selection) { - return getSelectionIndex( - getContext().getResources().getStringArray(arrayResId), selection); - } - - private void updateEnterpriseFields() { - int i; - String value; - for (i = AccessPointState.IDENTITY ; - i <= AccessPointState.ANONYMOUS_IDENTITY ; i++) { - value = mEnterpriseTextFields[i].getText().toString(); - if (!TextUtils.isEmpty(value)) { - mState.setEnterpriseField(i, value); - } - } - Spinner spinner = mClientCertSpinner; - int index = spinner.getSelectedItemPosition(); - if (index != (spinner.getCount() - 1)) { - String key = (String) spinner.getSelectedItem(); - mState.setEnterpriseField(AccessPointState.CLIENT_CERT, - KEYSTORE_HEADER + Credentials.USER_CERTIFICATE + key); - mState.setEnterpriseField(AccessPointState.PRIVATE_KEY, - KEYSTORE_HEADER + Credentials.USER_PRIVATE_KEY + key); - } - spinner = mCaCertSpinner; - index = spinner.getSelectedItemPosition(); - if (index != (spinner.getCount() - 1)) { - String key = (String) spinner.getSelectedItem(); - mState.setEnterpriseField(AccessPointState.CA_CERT, - KEYSTORE_HEADER + Credentials.CA_CERTIFICATE + key); - } - mState.setSecurity(AccessPointState.EAP); - mState.setEap(mEapSpinner.getSelectedItemPosition()); - mState.setPhase2((String) mPhase2Spinner.getSelectedItem()); - } - - /** - * Replaces our {@link #mState} with the equal WifiLayer instance. This is useful after - * we unparceled the state previously and before we are calling methods on {@link #mWifiLayer}. - * - * @return Whether WifiLayer was able to find an equal state in its set. - */ - private boolean replaceStateWithWifiLayerInstance() { - AccessPointState state = mWifiLayer.getWifiLayerApInstance(mState); - if (state == null) { - return false; - } - - mState = state; - return true; - } - - private int getSecurityTypeFromSpinner() { - int position = mSecuritySpinner.getSelectedItemPosition(); - // If there is no AUTO choice, the position needs 1 added to get - // to the proper spinner position -> security constants mapping - return mAutoSecurityAllowed ? position : position + 1; - } - - private String getEnteredPassword() { - return mPasswordEdit != null ? mPasswordEdit.getText().toString() : null; - } - - /** - * Call the one you want to hide first. - */ - private void setWepVisible(boolean visible) { - setGenericPasswordVisible(visible); - int visibility = visible ? View.VISIBLE : View.GONE; - mWepTypeSpinner.setVisibility(visibility); - } - - /** - * @see #setWepVisible(boolean) - */ - private void setGenericPasswordVisible(boolean visible) { - int visibility = visible ? View.VISIBLE : View.GONE; - mPasswordText.setVisibility(visibility); - mPasswordEdit.setVisibility(visibility); - mShowPasswordCheckBox.setVisibility(visibility); - } - - private void setEnterpriseFieldsVisible(boolean visible) { - int visibility = visible ? View.VISIBLE : View.GONE; - mEnterpriseView.setVisibility(visibility); - if (visible) { - setWepVisible(false); - } - if (mMode != MODE_CONFIGURE) { - mSsidText.setVisibility(View.GONE); - mSsidEdit.setVisibility(View.GONE); - mSecurityText.setVisibility(View.GONE); - mSecuritySpinner.setVisibility(View.GONE); - } - } - - public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (parent == mSecuritySpinner) { - handleSecurityChange(getSecurityTypeFromSpinner()); - } - } - - public void onNothingSelected(AdapterView parent) { - } - - private void handleSecurityChange(int security) { - setEnterpriseFieldsVisible(false); - switch (security) { - - case SECURITY_NONE: { - setWepVisible(false); - setGenericPasswordVisible(false); - break; - } - - case SECURITY_WEP: { - setGenericPasswordVisible(false); - setWepVisible(true); - updatePasswordCaption(AccessPointState.WEP); - break; - } - - case SECURITY_AUTO: { - setWepVisible(false); - setGenericPasswordVisible(mState.hasSecurity()); - // Shows the generic 'wireless password' - updatePasswordCaption(AccessPointState.PSK); - break; - } - - case SECURITY_PSK: { - setWepVisible(false); - setGenericPasswordVisible(true); - // Both WPA and WPA2 show the same caption, so either is ok - updatePasswordCaption(AccessPointState.PSK); - break; - } - case SECURITY_EAP: { - // Unlock the keystore if it is not unlocked yet. - if (mKeyStore.test() != KeyStore.NO_ERROR) { - Credentials.getInstance().unlock(getContext()); - return; - } - enableEnterpriseFields(); - break; - } - } - } - - private static int getSignalResId(int signal) { - switch (WifiManager.calculateSignalLevel(signal, 4)) { - case 0: { - return R.string.wifi_signal_0; - } - case 1: { - return R.string.wifi_signal_1; - } - case 2: { - return R.string.wifi_signal_2; - } - case 3: { - return R.string.wifi_signal_3; - } - } - - return 0; - } - - - public void onClick(View v) { - if (v == mShowPasswordCheckBox) { - setShowPassword(mShowPasswordCheckBox.isChecked()); - } - } - - private void setShowPassword(boolean showPassword) { - if (mPasswordEdit != null) { - mPasswordEdit.setInputType(InputType.TYPE_CLASS_TEXT | - (showPassword ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD - : InputType.TYPE_TEXT_VARIATION_PASSWORD)); - } - } - -} diff --git a/src/com/android/settings/wifi/AccessPointPreference.java b/src/com/android/settings/wifi/AccessPointPreference.java deleted file mode 100644 index 6dd5492..0000000 --- a/src/com/android/settings/wifi/AccessPointPreference.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.settings.R; - -import android.net.wifi.WifiManager; -import android.preference.Preference; -import android.view.View; -import android.widget.ImageView; - -public class AccessPointPreference extends Preference implements - AccessPointState.AccessPointStateCallback { - - // UI states - private static final int[] STATE_ENCRYPTED = { R.attr.state_encrypted }; - private static final int[] STATE_EMPTY = { }; - - // Signal strength indicator - private static final int UI_SIGNAL_LEVELS = 4; - - private AccessPointState mState; - - public AccessPointPreference(WifiSettings wifiSettings, AccessPointState state) { - super(wifiSettings, null); - - mState = state; - - setWidgetLayoutResource(R.layout.preference_widget_wifi_signal); - - state.setCallback(this); - - refresh(); - } - - public void refresh() { - setTitle(mState.getHumanReadableSsid()); - setSummary(mState.getSummarizedStatus()); - - notifyChanged(); - } - - public void refreshAccessPointState() { - refresh(); - - // The ordering of access points could have changed due to the state change, so - // re-evaluate ordering - notifyHierarchyChanged(); - } - - @Override - protected void onBindView(View view) { - super.onBindView(view); - - ImageView signal = (ImageView) view.findViewById(R.id.signal); - if (mState.seen) { - signal.setImageResource(R.drawable.wifi_signal); - signal.setImageState(mState.hasSecurity() ? STATE_ENCRYPTED : STATE_EMPTY, true); - signal.setImageLevel(getUiSignalLevel()); - } else { - signal.setImageDrawable(null); - } - } - - private int getUiSignalLevel() { - return mState != null ? WifiManager.calculateSignalLevel(mState.signal, UI_SIGNAL_LEVELS) - : 0; - } - - /** - * Returns the {@link AccessPointState} associated with this preference. - * @return The {@link AccessPointState}. - */ - public AccessPointState getAccessPointState() { - return mState; - } - - @Override - public int compareTo(Preference another) { - if (!(another instanceof AccessPointPreference)) { - // Let normal preferences go before us. - // NOTE: we should only be compared to Preference in our - // category. - return 1; - } - - return mState.compareTo(((AccessPointPreference) another).mState); - } - -} - diff --git a/src/com/android/settings/wifi/AccessPointState.java b/src/com/android/settings/wifi/AccessPointState.java deleted file mode 100644 index 5aefa55..0000000 --- a/src/com/android/settings/wifi/AccessPointState.java +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.settings.R; - -import android.content.Context; -import android.net.NetworkInfo; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiConfiguration.AuthAlgorithm; -import android.net.wifi.WifiConfiguration.GroupCipher; -import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.net.wifi.WifiConfiguration.PairwiseCipher; -import android.net.wifi.WifiConfiguration.Protocol; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; -import android.util.Log; - -public final class AccessPointState implements Comparable<AccessPointState>, Parcelable { - - private static final String TAG = "AccessPointState"; - - // Constants used for different security types - public static final String PSK = "PSK"; - public static final String WEP = "WEP"; - public static final String EAP = "EAP"; - public static final String OPEN = "Open"; - - public static final String[] EAP_METHOD = { "PEAP", "TLS", "TTLS" }; - - /** String present in capabilities if the scan result is ad-hoc */ - private static final String ADHOC_CAPABILITY = "[IBSS]"; - /** String present in capabilities if the scan result is enterprise secured */ - private static final String ENTERPRISE_CAPABILITY = "-EAP-"; - - public static final String BSSID_ANY = "any"; - public static final int NETWORK_ID_NOT_SET = -1; - /** This should be used with care! */ - static final int NETWORK_ID_ANY = -2; - - public static final int MATCH_NONE = 0; - public static final int MATCH_WEAK = 1; - public static final int MATCH_STRONG = 2; - public static final int MATCH_EXACT = 3; - - // Don't set these directly, use the setters. - public int networkId; - public int priority; - public boolean hiddenSsid; - public int linkSpeed; - public int ipAddress; - public String bssid; - public String ssid; - public int signal; - public boolean primary; - public boolean seen; - public boolean configured; - public NetworkInfo.DetailedState status; - public String security; - public boolean disabled; - - /** - * Use this for sorting based on signal strength. It is a heavily-damped - * time-averaged weighted signal. - */ - private float signalForSorting = Float.MIN_VALUE; - - private static final float DAMPING_FACTOR = 0.2f; - - /** - * This will be a user entered password, and NOT taken from wpa_supplicant - * (since it would give us *) - */ - private String mPassword; - private boolean mConfigHadPassword; - - public static final int WEP_PASSWORD_AUTO = 0; - public static final int WEP_PASSWORD_ASCII = 1; - public static final int WEP_PASSWORD_HEX = 2; - private int mWepPasswordType; - - /* Enterprise Fields */ - public static final int IDENTITY = 0; - public static final int ANONYMOUS_IDENTITY = 1; - public static final int CLIENT_CERT = 2; - public static final int CA_CERT = 3; - public static final int PRIVATE_KEY = 4; - public static final int MAX_ENTRPRISE_FIELD = 5; - private String mEnterpriseFields[] = new String[MAX_ENTRPRISE_FIELD]; - private String mEap; - private String mPhase2; - - private Context mContext; - - /** - * If > 0, don't refresh (changes are being batched), use - * {@link #blockRefresh()} and {@link #unblockRefresh()} only. - */ - private int mBlockRefresh; - /** - * This will be set by {@link #requestRefresh} and shouldn't be written to - * elsewhere. - */ - private boolean mNeedsRefresh; - - private AccessPointStateCallback mCallback; - - private StringBuilder mSummaryBuilder = new StringBuilder(); - - interface AccessPointStateCallback { - void refreshAccessPointState(); - } - - public AccessPointState(Context context) { - this(); - - setContext(context); - } - - private AccessPointState() { - bssid = BSSID_ANY; - ssid = ""; - networkId = NETWORK_ID_NOT_SET; - hiddenSsid = false; - } - - void setContext(Context context) { - mContext = context; - } - - public void setNetworkId(int networkId) { - this.networkId = networkId; - } - - public void setBssid(String bssid) { - if (bssid != null) { - // If the BSSID is a wildcard, do NOT let a specific BSSID replace it - if (!this.bssid.equals(BSSID_ANY)) { - this.bssid = bssid; - } - } - } - - private String getWpaSupplicantBssid() { - return bssid.equals(BSSID_ANY) ? null : bssid; - } - - public static String convertToQuotedString(String string) { - if (TextUtils.isEmpty(string)) { - return ""; - } - - final int lastPos = string.length() - 1; - if (lastPos < 0 || (string.charAt(0) == '"' && string.charAt(lastPos) == '"')) { - return string; - } - - return "\"" + string + "\""; - } - - public void setPrimary(boolean primary) { - if (this.primary != primary) { - this.primary = primary; - requestRefresh(); - } - } - - public void setSeen(boolean seen) { - if (this.seen != seen) { - this.seen = seen; - requestRefresh(); - } - } - - public void setDisabled(boolean disabled) { - if (this.disabled != disabled) { - this.disabled = disabled; - requestRefresh(); - } - } - - public void setSignal(int signal) { - - if (signalForSorting == Float.MIN_VALUE) { - signalForSorting = signal; - } else { - signalForSorting = (DAMPING_FACTOR * signal) + ((1-DAMPING_FACTOR) * signalForSorting); - } - - if (this.signal != signal) { - this.signal = signal; - requestRefresh(); - } - } - - public String getHumanReadableSsid() { - if (TextUtils.isEmpty(ssid)) { - return ""; - } - - final int lastPos = ssid.length() - 1; - if (ssid.charAt(0) == '"' && ssid.charAt(lastPos) == '"') { - return ssid.substring(1, lastPos); - } - - return ssid; - } - - public void setSsid(String ssid) { - if (ssid != null) { - this.ssid = convertToQuotedString(ssid); - requestRefresh(); - } - } - - public void setPriority(int priority) { - if (this.priority != priority) { - this.priority = priority; - requestRefresh(); - } - } - - public void setHiddenSsid(boolean hiddenSsid) { - if (this.hiddenSsid != hiddenSsid) { - this.hiddenSsid = hiddenSsid; - requestRefresh(); - } - } - - public void setLinkSpeed(int linkSpeed) { - if (this.linkSpeed != linkSpeed) { - this.linkSpeed = linkSpeed; - requestRefresh(); - } - } - - public void setIpAddress(int address) { - if (ipAddress != address) { - ipAddress = address; - requestRefresh(); - } - } - - public void setConfigured(boolean configured) { - if (this.configured != configured) { - this.configured = configured; - requestRefresh(); - } - } - - public void setStatus(NetworkInfo.DetailedState status) { - if (this.status != status) { - this.status = status; - requestRefresh(); - } - } - - public boolean isEnterprise() { - return (AccessPointState.EAP.equals(security)); - } - - public void setSecurity(String security) { - if (TextUtils.isEmpty(this.security) || !this.security.equals(security)) { - this.security = security; - requestRefresh(); - } - } - - public boolean hasSecurity() { - return security != null && !security.contains(OPEN); - } - - public String getHumanReadableSecurity() { - if (security.equals(OPEN)) return mContext.getString(R.string.wifi_security_open); - else if (security.equals(WEP)) return mContext.getString(R.string.wifi_security_wep); - else if (security.equals(PSK)) return mContext.getString(R.string.wifi_security_psk); - else if (security.equals(EAP)) return mContext.getString(R.string.wifi_security_eap); - - return mContext.getString(R.string.wifi_security_unknown); - } - - public void updateFromScanResult(ScanResult scanResult) { - blockRefresh(); - - // We don't keep specific AP BSSIDs and instead leave that as wildcard - - setSeen(true); - setSsid(scanResult.SSID); - if (networkId == NETWORK_ID_NOT_SET) { - // Since ScanResults don't cross-reference network ID, we set it as a wildcard - setNetworkId(NETWORK_ID_ANY); - } - setSignal(scanResult.level); - setSecurity(getScanResultSecurity(scanResult)); - unblockRefresh(); - } - - /** - * @return The security of a given {@link ScanResult}. - */ - public static String getScanResultSecurity(ScanResult scanResult) { - final String cap = scanResult.capabilities; - final String[] securityModes = { WEP, PSK, EAP }; - for (int i = securityModes.length - 1; i >= 0; i--) { - if (cap.contains(securityModes[i])) { - return securityModes[i]; - } - } - - return OPEN; - } - - /** - * @return Whether the given ScanResult represents an adhoc network. - */ - public static boolean isAdhoc(ScanResult scanResult) { - return scanResult.capabilities.contains(ADHOC_CAPABILITY); - } - - /** - * @return Whether the given ScanResult has enterprise security. - */ - public static boolean isEnterprise(ScanResult scanResult) { - return scanResult.capabilities.contains(ENTERPRISE_CAPABILITY); - } - - public void updateFromWifiConfiguration(WifiConfiguration wifiConfig) { - if (wifiConfig != null) { - blockRefresh(); - setBssid(wifiConfig.BSSID); - setNetworkId(wifiConfig.networkId); - setPriority(wifiConfig.priority); - setHiddenSsid(wifiConfig.hiddenSSID); - setSsid(wifiConfig.SSID); - setConfigured(true); - setDisabled(wifiConfig.status == WifiConfiguration.Status.DISABLED); - parseWifiConfigurationSecurity(wifiConfig); - unblockRefresh(); - } - } - - public void setPassword(String password) { - setPassword(password, WEP_PASSWORD_AUTO); - } - - public void setPassword(String password, int wepPasswordType) { - mPassword = password; - mWepPasswordType = wepPasswordType; - } - - /* For Enterprise Fields */ - public void setEnterpriseField(int field, String value) { - if ((value != null) && (field >= 0) && (field < MAX_ENTRPRISE_FIELD)) { - this.mEnterpriseFields[field] = value; - requestRefresh(); - } - } - - public void setPhase2(String phase2) { - if (!TextUtils.isEmpty(phase2) && (!phase2.equals("None"))) { - mPhase2 = phase2; - } - } - - public String getPhase2() { - return mPhase2; - } - - public void setEap(int method) { - mEap = EAP_METHOD[method]; - requestRefresh(); - } - - public String getEap() { - return mEap; - } - public String getEnterpriseField(int field) { - if(field >=0 && field < MAX_ENTRPRISE_FIELD) { - return mEnterpriseFields[field]; - } - return null; - } - - public boolean hasPassword() { - return !TextUtils.isEmpty(mPassword) || mConfigHadPassword; - } - - private static boolean hasPassword(WifiConfiguration wifiConfig) { - return !TextUtils.isEmpty(wifiConfig.preSharedKey) - || !TextUtils.isEmpty(wifiConfig.wepKeys[0]) - || !TextUtils.isEmpty(wifiConfig.wepKeys[1]) - || !TextUtils.isEmpty(wifiConfig.wepKeys[2]) - || !TextUtils.isEmpty(wifiConfig.wepKeys[3]); - } - - private void parseWifiConfigurationSecurity(WifiConfiguration wifiConfig) { - setSecurity(getWifiConfigurationSecurity(wifiConfig)); - mConfigHadPassword = hasPassword(wifiConfig); - } - - /** - * @return The security of a given {@link WifiConfiguration}. - */ - public static String getWifiConfigurationSecurity(WifiConfiguration wifiConfig) { - if (!TextUtils.isEmpty(wifiConfig.eap.value())) { - return EAP; - } else if (!TextUtils.isEmpty(wifiConfig.preSharedKey)) { - return PSK; - } else if (!TextUtils.isEmpty(wifiConfig.wepKeys[0])) { - return WEP; - } - return OPEN; - } - - public void updateFromWifiInfo(WifiInfo wifiInfo, NetworkInfo.DetailedState state) { - if (wifiInfo != null) { - blockRefresh(); - setBssid(wifiInfo.getBSSID()); - setLinkSpeed(wifiInfo.getLinkSpeed()); - setNetworkId(wifiInfo.getNetworkId()); - setIpAddress(wifiInfo.getIpAddress()); - setSsid(wifiInfo.getSSID()); - if (state != null) { - setStatus(state); - } - setHiddenSsid(wifiInfo.getHiddenSSID()); - unblockRefresh(); - } - } - - /** - * @return Whether this AP can be connected to at the moment. - */ - public boolean isConnectable() { - return !primary && seen; - } - - /** - * @return Whether this AP can be forgotten at the moment. - */ - public boolean isForgetable() { - return configured; - } - - /** - * Updates the state as if it were never configured. - * <p> - * Note: This will not pass the forget call to the Wi-Fi API. - */ - public void forget() { - blockRefresh(); - setConfigured(false); - setNetworkId(NETWORK_ID_NOT_SET); - setPrimary(false); - setStatus(null); - setDisabled(false); - unblockRefresh(); - } - - public void updateWifiConfiguration(WifiConfiguration config) { - config.BSSID = getWpaSupplicantBssid(); - config.priority = priority; - config.hiddenSSID = hiddenSsid; - config.SSID = convertToQuotedString(ssid); - config.eap.setValue(mEap); - - if (!TextUtils.isEmpty(mPhase2)) { - config.phase2.setValue(convertToQuotedString("auth=" + mPhase2)); - } else { - config.phase2.setValue(null); - } - if (!TextUtils.isEmpty(mEnterpriseFields[IDENTITY])) { - config.identity.setValue( - convertToQuotedString(mEnterpriseFields[IDENTITY])); - } else { - config.identity.setValue(null); - } - if (!TextUtils.isEmpty(mEnterpriseFields[ANONYMOUS_IDENTITY])) { - config.anonymous_identity.setValue(convertToQuotedString( - mEnterpriseFields[ANONYMOUS_IDENTITY])); - } else { - config.anonymous_identity.setValue(null); - } - if (!TextUtils.isEmpty(mEnterpriseFields[CLIENT_CERT])) { - config.client_cert.setValue(convertToQuotedString( - mEnterpriseFields[CLIENT_CERT])); - } else { - config.client_cert.setValue(null); - } - if (!TextUtils.isEmpty(mEnterpriseFields[CA_CERT])) { - config.ca_cert.setValue(convertToQuotedString( - mEnterpriseFields[CA_CERT])); - } else { - config.ca_cert.setValue(null); - } - if (!TextUtils.isEmpty(mEnterpriseFields[PRIVATE_KEY])) { - config.private_key.setValue(convertToQuotedString( - mEnterpriseFields[PRIVATE_KEY])); - } else { - config.private_key.setValue(null); - } - setupSecurity(config); - } - - private void setupSecurity(WifiConfiguration config) { - config.allowedAuthAlgorithms.clear(); - config.allowedGroupCiphers.clear(); - config.allowedKeyManagement.clear(); - config.allowedPairwiseCiphers.clear(); - config.allowedProtocols.clear(); - - if (TextUtils.isEmpty(security)) { - security = OPEN; - Log.w(TAG, "Empty security, assuming open"); - } - - if (security.equals(WEP)) { - // If password is empty, it should be left untouched - if (!TextUtils.isEmpty(mPassword)) { - if (mWepPasswordType == WEP_PASSWORD_AUTO) { - if (isHexWepKey(mPassword)) { - config.wepKeys[0] = mPassword; - } else { - config.wepKeys[0] = convertToQuotedString(mPassword); - } - } else { - config.wepKeys[0] = mWepPasswordType == WEP_PASSWORD_ASCII - ? convertToQuotedString(mPassword) - : mPassword; - } - } - config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); - config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED); - config.allowedKeyManagement.set(KeyMgmt.NONE); - config.wepTxKeyIndex = 0; - } else if (security.equals(PSK)){ - // If password is empty, it should be left untouched - if (!TextUtils.isEmpty(mPassword)) { - if (mPassword.length() == 64 && isHex(mPassword)) { - // Goes unquoted as hex - config.preSharedKey = mPassword; - } else { - // Goes quoted as ASCII - config.preSharedKey = convertToQuotedString(mPassword); - } - } - } else if (security.equals(EAP)) { - config.allowedKeyManagement.set(KeyMgmt.WPA_EAP); - config.allowedKeyManagement.set(KeyMgmt.IEEE8021X); - if (!TextUtils.isEmpty(mPassword)) { - config.password.setValue(convertToQuotedString(mPassword)); - } - } else if (security.equals(OPEN)) { - config.allowedKeyManagement.set(KeyMgmt.NONE); - } - } - - private static boolean isHexWepKey(String wepKey) { - final int len = wepKey.length(); - - // WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?) - if (len != 10 && len != 26 && len != 58) { - return false; - } - - return isHex(wepKey); - } - - private static boolean isHex(String key) { - for (int i = key.length() - 1; i >= 0; i--) { - final char c = key.charAt(i); - if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f')) { - return false; - } - } - - return true; - } - - public void setCallback(AccessPointStateCallback callback) { - mCallback = callback; - } - - void blockRefresh() { - mBlockRefresh++; - } - - void unblockRefresh() { - if (--mBlockRefresh == 0 && mNeedsRefresh) { - requestRefresh(); - } - } - - private void requestRefresh() { - if (mBlockRefresh > 0) { - mNeedsRefresh = true; - return; - } - - if (mCallback != null) { - mCallback.refreshAccessPointState(); - } - - mNeedsRefresh = false; - } - - /** - * {@inheritDoc} - * @see #hashCode() - * @see #equals(Object) - */ - public int matches(int otherNetworkId, String otherBssid, String otherSsid, - String otherSecurity) { - - // Whenever this method is touched, please ensure #equals and #hashCode - // still work with the changes here! - - if (otherSsid == null) { - if (WifiLayer.LOGV) { - Log.w(TAG, "BSSID: " + otherBssid + ", SSID: " + otherSsid); - } - return MATCH_NONE; - } - - /* - * If we both have 'security' set, it must match (an open network still - * has 'security' set to OPEN) - */ - if (security != null && otherSecurity != null) { - if (!security.equals(otherSecurity)) { - return MATCH_NONE; - } - } - - // WifiConfiguration gives an empty bssid as a BSSID wildcard - if (TextUtils.isEmpty(otherBssid)) { - otherBssid = AccessPointState.BSSID_ANY; - } - - final boolean networkIdMatches = networkId == otherNetworkId; - if (!networkIdMatches && networkId != NETWORK_ID_ANY && otherNetworkId != NETWORK_ID_ANY) { - // Network IDs don't match (e.g., 1 & 2 or unset & 1) and neither is a wildcard - return MATCH_NONE; - } - - if (networkIdMatches && otherNetworkId != NETWORK_ID_NOT_SET - && otherNetworkId != NETWORK_ID_ANY) { - // Network ID matches (they're set to the same ID) - return MATCH_EXACT; - } - - // So now, network IDs aren't set or at least one is a wildcard - - final boolean bssidMatches = bssid.equals(otherBssid); - final boolean otherBssidIsWildcard = otherBssid.equals(BSSID_ANY); - if (bssidMatches && !otherBssidIsWildcard) { - // BSSID matches (and neither is a wildcard) - return MATCH_STRONG; - } - - if (!bssidMatches && !bssid.equals(BSSID_ANY) && !otherBssidIsWildcard) { - // BSSIDs don't match (e.g., 00:24:21:21:42:12 & 42:12:44:21:22:52) - // and neither is a wildcard - return MATCH_NONE; - } - - // So now, BSSIDs are both wildcards - - final boolean ssidMatches = ssid.equals(otherSsid); - if (ssidMatches) { - // SSID matches - return MATCH_WEAK; - } - - return MATCH_NONE; - } - - /** - * {@inheritDoc} - * @see #matches(int, String, String) - * @see #equals(Object) - */ - @Override - public int hashCode() { - // Two equal() objects must have same hashCode. - // With Wi-Fi, the broadest match is if two SSIDs are the same. The finer-grained matches - // imply this (for example, the same network IDs means the same WifiConfiguration which - // means the same SSID). - // See #matches for the exact matching algorithm we use. - return ssid != null ? ssid.hashCode() : 0; - } - - /** - * {@inheritDoc} - * @see #matches(int, String, String) - * @see #hashCode() - */ - @Override - public boolean equals(Object o) { - if (!o.getClass().equals(getClass())) { - return false; - } - - final AccessPointState other = (AccessPointState) o; - - // To see which conditions cause two AccessPointStates to be equal, see - // where #matches returns MATCH_WEAK or greater. - - return matches(other.networkId, other.bssid, other.ssid, other.security) >= MATCH_WEAK; - } - - public int matchesWifiConfiguration(WifiConfiguration wifiConfig) { - String security = getWifiConfigurationSecurity(wifiConfig); - return matches(wifiConfig.networkId, wifiConfig.BSSID, wifiConfig.SSID, security); - } - - String getSummarizedStatus() { - StringBuilder sb = mSummaryBuilder; - sb.delete(0, sb.length()); - - if (primary && status != null) { - buildSummary(sb, WifiStatus.getPrintable(mContext, status), true); - - } else if (!seen) { - buildSummary(sb, mContext.getString(R.string.summary_not_in_range), true); - - // Remembered comes second in this case - if (!primary && configured) { - buildSummary(sb, mContext.getString(R.string.summary_remembered), true); - } - - } else { - if (configured && disabled) { - // The connection failure overrides all in this case - return mContext.getString(R.string.summary_connection_failed); - } - - // Remembered comes first in this case - if (!primary && configured) { - buildSummary(sb, mContext.getString(R.string.summary_remembered), true); - } - - // If it is seen (and not the primary), show the security type - String verboseSecurity = getVerboseSecurity(); - if (verboseSecurity != null) { - buildSummary(sb, verboseSecurity, true); - } - } - - return sb.toString(); - } - - private String getVerboseSecurity() { - if (WEP.equals(security)) { - return mContext.getString(R.string.wifi_security_verbose_wep); - } else if (PSK.equals(security)) { - return mContext.getString(R.string.wifi_security_verbose_psk); - } else if (EAP.equals(security)) { - return mContext.getString(R.string.wifi_security_verbose_eap); - } else { - return null; - } - } - - private void buildSummary(StringBuilder sb, String string, boolean autoUpperCaseFirstLetter) { - if (sb.length() == 0) { - if (autoUpperCaseFirstLetter && string.length() > 1 - && Character.isLowerCase(string.charAt(0)) - && !Character.isUpperCase(string.charAt(1))) { - sb.append(Character.toUpperCase(string.charAt(0))).append(string, 1, - string.length()); - } else { - sb.append(string); - } - } else { - sb.append(", "); - sb.append(string); - } - } - - public int compareTo(AccessPointState other) { - // This ranks the states for displaying in the AP list, not for - // connecting to (wpa_supplicant does that using the WifiConfiguration's - // priority field). - - // Clarity > efficiency, of this logic: - int comparison; - - // Primary - comparison = (other.primary ? 1 : 0) - (primary ? 1 : 0); - if (comparison != 0) return comparison; - - // Currently seen (similar to, but not always the same as within range) - comparison = (other.seen ? 1 : 0) - (seen ? 1 : 0); - if (comparison != 0) return comparison; - - // Configured - comparison = (other.configured ? 1 : 0) - (configured ? 1 : 0); - if (comparison != 0) return comparison; - - if (!configured) { - // Neither are configured - - // Open network - comparison = (hasSecurity() ? 1 : 0) - (other.hasSecurity() ? 1 : 0); - if (comparison != 0) return comparison; - } - - // Signal strength - comparison = (int) (other.signalForSorting - signalForSorting); - if (comparison != 0) return comparison; - - // Alphabetical - return ssid.compareToIgnoreCase(other.ssid); - } - - public String toString() { - return ssid + " (" + bssid + ", " + networkId + ", " + super.toString() + ")"; - } - - /** Implement the Parcelable interface */ - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(bssid); - dest.writeInt(configured ? 1 : 0); - dest.writeInt(ipAddress); - dest.writeInt(linkSpeed); - dest.writeInt(networkId); - dest.writeInt(primary ? 1 : 0); - dest.writeInt(priority); - dest.writeInt(hiddenSsid ? 1 : 0); - dest.writeString(security); - dest.writeInt(seen ? 1 : 0); - dest.writeInt(disabled ? 1 : 0); - dest.writeInt(signal); - dest.writeString(ssid); - dest.writeString(status != null ? status.toString() : null); - dest.writeString(mPassword); - dest.writeInt(mConfigHadPassword ? 1 : 0); - dest.writeInt(mWepPasswordType); - } - - /** Implement the Parcelable interface */ - public int describeContents() { - return 0; - } - - /** Implement the Parcelable interface */ - public static final Creator<AccessPointState> CREATOR = - new Creator<AccessPointState>() { - public AccessPointState createFromParcel(Parcel in) { - AccessPointState state = new AccessPointState(); - state.bssid = in.readString(); - state.configured = in.readInt() == 1; - state.ipAddress = in.readInt(); - state.linkSpeed = in.readInt(); - state.networkId = in.readInt(); - state.primary = in.readInt() == 1; - state.priority = in.readInt(); - state.hiddenSsid = in.readInt() == 1; - state.security = in.readString(); - state.seen = in.readInt() == 1; - state.disabled = in.readInt() == 1; - state.signal = in.readInt(); - state.ssid = in.readString(); - String statusStr = in.readString(); - if (statusStr != null) { - state.status = NetworkInfo.DetailedState.valueOf(statusStr); - } - state.mPassword = in.readString(); - state.mConfigHadPassword = in.readInt() == 1; - state.mWepPasswordType = in.readInt(); - return state; - } - - public AccessPointState[] newArray(int size) { - return new AccessPointState[size]; - } - }; - - -} diff --git a/src/com/android/settings/wifi/AdvancedSettings.java b/src/com/android/settings/wifi/AdvancedSettings.java index cca10da..636e1df 100644 --- a/src/com/android/settings/wifi/AdvancedSettings.java +++ b/src/com/android/settings/wifi/AdvancedSettings.java @@ -34,6 +34,7 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; +import android.os.SystemProperties; public class AdvancedSettings extends PreferenceActivity implements Preference.OnPreferenceChangeListener { @@ -58,6 +59,9 @@ public class AdvancedSettings extends PreferenceActivity private static final int MENU_ITEM_SAVE = Menu.FIRST; private static final int MENU_ITEM_CANCEL = Menu.FIRST + 1; + //Tracks ro.debuggable (1 on userdebug builds) + private static int DEBUGGABLE; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -72,19 +76,24 @@ public class AdvancedSettings extends PreferenceActivity preference.setOnPreferenceChangeListener(this); } -// /* -// * Fix the Run-time IllegalStateException that ListPreference requires an entries -// * array and an entryValues array, this exception occurs when user open/close the -// * slider in the Regulatory domain dialog. -// */ -// initNumChannelsPreference(); + DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0); + /** * Remove user control of regulatory domain - * channel count settings + * channel count settings in non userdebug builds */ - Preference chanPref = findPreference(KEY_NUM_CHANNELS); - if (chanPref != null) { - getPreferenceScreen().removePreference(chanPref); + if (DEBUGGABLE == 1) { + /* + * Fix the Run-time IllegalStateException that ListPreference requires an entries + * array and an entryValues array, this exception occurs when user open/close the + * slider in the Regulatory domain dialog. + */ + initNumChannelsPreference(); + } else { + Preference chanPref = findPreference(KEY_NUM_CHANNELS); + if (chanPref != null) { + getPreferenceScreen().removePreference(chanPref); + } } } @@ -95,9 +104,11 @@ public class AdvancedSettings extends PreferenceActivity updateUi(); /** * Remove user control of regulatory domain - * channel count settings + * channel count settings in non userdebug builds */ - //initNumChannelsPreference(); + if (DEBUGGABLE == 1) { + initNumChannelsPreference(); + } initSleepPolicyPreference(); refreshWifiInfo(); } diff --git a/src/com/android/settings/wifi/Summary.java b/src/com/android/settings/wifi/Summary.java new file mode 100644 index 0000000..6da2fa5 --- /dev/null +++ b/src/com/android/settings/wifi/Summary.java @@ -0,0 +1,40 @@ +/* + * 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.wifi; + +import com.android.settings.R; + +import android.content.Context; +import android.net.NetworkInfo.DetailedState; +import android.text.TextUtils; + +class Summary { + static String get(Context context, String ssid, DetailedState state) { + String[] formats = context.getResources().getStringArray((ssid == null) + ? R.array.wifi_status : R.array.wifi_status_with_ssid); + int index = state.ordinal(); + + if (index >= formats.length || formats[index].length() == 0) { + return null; + } + return String.format(formats[index], ssid); + } + + static String get(Context context, DetailedState state) { + return get(context, null, state); + } +} diff --git a/src/com/android/settings/wifi/WifiAPITest.java b/src/com/android/settings/wifi/WifiAPITest.java new file mode 100644 index 0000000..4a9a075 --- /dev/null +++ b/src/com/android/settings/wifi/WifiAPITest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009 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 com.android.settings.R; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.wifi.WifiManager; + +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.text.Editable; +import android.widget.EditText; + + +/** + * Provide an interface for testing out the Wifi API + */ +public class WifiAPITest extends PreferenceActivity implements +Preference.OnPreferenceClickListener { + + private static final String TAG = "WifiAPITest"; + private int netid; + + //============================ + // Preference/activity member variables + //============================ + + private static final String KEY_DISCONNECT = "disconnect"; + private static final String KEY_DISABLE_NETWORK = "disable_network"; + private static final String KEY_ENABLE_NETWORK = "enable_network"; + + private Preference mWifiDisconnect; + private Preference mWifiDisableNetwork; + private Preference mWifiEnableNetwork; + + private WifiManager mWifiManager; + + + //============================ + // Activity lifecycle + //============================ + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + onCreatePreferences(); + mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE); + } + + + private void onCreatePreferences() { + addPreferencesFromResource(R.layout.wifi_api_test); + + final PreferenceScreen preferenceScreen = getPreferenceScreen(); + + mWifiDisconnect = (Preference) preferenceScreen.findPreference(KEY_DISCONNECT); + mWifiDisconnect.setOnPreferenceClickListener(this); + + mWifiDisableNetwork = (Preference) preferenceScreen.findPreference(KEY_DISABLE_NETWORK); + mWifiDisableNetwork.setOnPreferenceClickListener(this); + + mWifiEnableNetwork = (Preference) preferenceScreen.findPreference(KEY_ENABLE_NETWORK); + mWifiEnableNetwork.setOnPreferenceClickListener(this); + + } + + //============================ + // Preference callbacks + //============================ + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { + super.onPreferenceTreeClick(preferenceScreen, preference); + return false; + } + + /** + * Implements OnPreferenceClickListener interface + */ + public boolean onPreferenceClick(Preference pref) { + if (pref == mWifiDisconnect) { + mWifiManager.disconnect(); + } else if (pref == mWifiDisableNetwork) { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + alert.setTitle("Input"); + alert.setMessage("Enter Network ID"); + // Set an EditText view to get user input + final EditText input = new EditText(this); + alert.setView(input); + alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + Editable value = input.getText(); + netid = Integer.parseInt(value.toString()); + mWifiManager.disableNetwork(netid); + } + }); + alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // Canceled. + } + }); + alert.show(); + } else if (pref == mWifiEnableNetwork) { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + alert.setTitle("Input"); + alert.setMessage("Enter Network ID"); + // Set an EditText view to get user input + final EditText input = new EditText(this); + alert.setView(input); + alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + Editable value = input.getText(); + netid = Integer.parseInt(value.toString()); + mWifiManager.enableNetwork(netid, false); + } + }); + alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // Canceled. + } + }); + alert.show(); + } + return true; + } +} diff --git a/src/com/android/settings/wifi/WifiApDialog.java b/src/com/android/settings/wifi/WifiApDialog.java new file mode 100644 index 0000000..43289d2 --- /dev/null +++ b/src/com/android/settings/wifi/WifiApDialog.java @@ -0,0 +1,189 @@ +/* + * 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.wifi; + +import com.android.settings.R; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.AuthAlgorithm; +import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.os.Bundle; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +/** + * Dialog to configure the SSID and security settings + * for Access Point operation + */ +class WifiApDialog extends AlertDialog implements View.OnClickListener, + TextWatcher, AdapterView.OnItemSelectedListener { + + static final int BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE; + + private final DialogInterface.OnClickListener mListener; + + private static final int OPEN_INDEX = 0; + private static final int WPA_INDEX = 1; + + private View mView; + private TextView mSsid; + private int mSecurityType = AccessPoint.SECURITY_NONE; + private EditText mPassword; + + WifiConfiguration mWifiConfig; + + public WifiApDialog(Context context, DialogInterface.OnClickListener listener, + WifiConfiguration wifiConfig) { + super(context); + mListener = listener; + mWifiConfig = wifiConfig; + if (wifiConfig != null) + mSecurityType = AccessPoint.getSecurity(wifiConfig); + } + + public WifiConfiguration getConfig() { + + WifiConfiguration config = new WifiConfiguration(); + + /** + * TODO: SSID in WifiConfiguration for soft ap + * is being stored as a raw string without quotes. + * This is not the case on the client side. We need to + * make things consistent and clean it up + */ + config.SSID = mSsid.getText().toString(); + + switch (mSecurityType) { + case AccessPoint.SECURITY_NONE: + config.allowedKeyManagement.set(KeyMgmt.NONE); + return config; + + case AccessPoint.SECURITY_PSK: + config.allowedKeyManagement.set(KeyMgmt.WPA_PSK); + config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); + if (mPassword.length() != 0) { + String password = mPassword.getText().toString(); + config.preSharedKey = password; + } + return config; + } + return null; + } + + protected void onCreate(Bundle savedInstanceState) { + + mView = getLayoutInflater().inflate(R.layout.wifi_ap_dialog, null); + Spinner mSecurity = ((Spinner) mView.findViewById(R.id.security)); + + setView(mView); + setInverseBackgroundForced(true); + + Context context = getContext(); + + setTitle(R.string.wifi_tether_configure_ap_text); + mView.findViewById(R.id.type).setVisibility(View.VISIBLE); + mSsid = (TextView) mView.findViewById(R.id.ssid); + mPassword = (EditText) mView.findViewById(R.id.password); + + setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_save), mListener); + setButton(DialogInterface.BUTTON_NEGATIVE, + context.getString(R.string.wifi_cancel), mListener); + + if (mWifiConfig != null) { + mSsid.setText(mWifiConfig.SSID); + switch (mSecurityType) { + case AccessPoint.SECURITY_NONE: + mSecurity.setSelection(OPEN_INDEX); + break; + case AccessPoint.SECURITY_PSK: + String str = mWifiConfig.preSharedKey; + mPassword.setText(str); + mSecurity.setSelection(WPA_INDEX); + break; + } + } + + mSsid.addTextChangedListener(this); + mPassword.addTextChangedListener(this); + ((CheckBox) mView.findViewById(R.id.show_password)).setOnClickListener(this); + mSecurity.setOnItemSelectedListener(this); + + super.onCreate(savedInstanceState); + + showSecurityFields(); + validate(); + } + + private void validate() { + if ((mSsid != null && mSsid.length() == 0) || + (mSecurityType == AccessPoint.SECURITY_PSK && mPassword.length() < 8)) { + getButton(BUTTON_SUBMIT).setEnabled(false); + } else { + getButton(BUTTON_SUBMIT).setEnabled(true); + } + } + + public void onClick(View view) { + mPassword.setInputType( + InputType.TYPE_CLASS_TEXT | (((CheckBox) view).isChecked() ? + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : + InputType.TYPE_TEXT_VARIATION_PASSWORD)); + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void afterTextChanged(Editable editable) { + validate(); + } + + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if(position == OPEN_INDEX) + mSecurityType = AccessPoint.SECURITY_NONE; + else + mSecurityType = AccessPoint.SECURITY_PSK; + showSecurityFields(); + validate(); + } + + public void onNothingSelected(AdapterView parent) { + } + + private void showSecurityFields() { + if (mSecurityType == AccessPoint.SECURITY_NONE) { + mView.findViewById(R.id.fields).setVisibility(View.GONE); + return; + } + mView.findViewById(R.id.fields).setVisibility(View.VISIBLE); + } +} diff --git a/src/com/android/settings/wifi/WifiApEnabler.java b/src/com/android/settings/wifi/WifiApEnabler.java new file mode 100644 index 0000000..e907cf7 --- /dev/null +++ b/src/com/android/settings/wifi/WifiApEnabler.java @@ -0,0 +1,214 @@ +/* + * 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.wifi; + +import com.android.settings.R; +import com.android.settings.WirelessSettings; + +import java.util.ArrayList; + +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.SupplicantState; +import android.net.wifi.WifiConfiguration; +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.util.Log; +import android.widget.Toast; + +public class WifiApEnabler implements Preference.OnPreferenceChangeListener { + private final Context mContext; + private final CheckBoxPreference mCheckBox; + private final CharSequence mOriginalSummary; + + private WifiManager mWifiManager; + private final IntentFilter mIntentFilter; + + ConnectivityManager mCm; + private String[] mWifiRegexs; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(action)) { + handleWifiApStateChanged(intent.getIntExtra( + WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED)); + } else if (ConnectivityManager.ACTION_TETHER_STATE_CHANGED.equals(action)) { + ArrayList<String> available = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_AVAILABLE_TETHER); + ArrayList<String> active = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ACTIVE_TETHER); + ArrayList<String> errored = intent.getStringArrayListExtra( + ConnectivityManager.EXTRA_ERRORED_TETHER); + updateTetherState(available.toArray(), active.toArray(), errored.toArray()); + } + + } + }; + + public WifiApEnabler(Context context, CheckBoxPreference checkBox) { + mContext = context; + mCheckBox = checkBox; + mOriginalSummary = checkBox.getSummary(); + checkBox.setPersistent(false); + + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + + mWifiRegexs = mCm.getTetherableWifiRegexs(); + + mIntentFilter = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); + mIntentFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); + } + + public void resume() { + mContext.registerReceiver(mReceiver, mIntentFilter); + enableWifiCheckBox(); + mCheckBox.setOnPreferenceChangeListener(this); + } + + public void pause() { + mContext.unregisterReceiver(mReceiver); + mCheckBox.setOnPreferenceChangeListener(null); + } + + private void enableWifiCheckBox() { + boolean isAirplaneMode = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.AIRPLANE_MODE_ON, 0) != 0; + if(!isAirplaneMode) { + mCheckBox.setEnabled(true); + } else { + mCheckBox.setEnabled(false); + } + } + + public boolean onPreferenceChange(Preference preference, Object value) { + + final ContentResolver cr = mContext.getContentResolver(); + boolean enable = (Boolean)value; + + /** + * Disable Wifi if enabling tethering + */ + int wifiState = mWifiManager.getWifiState(); + if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) || + (wifiState == WifiManager.WIFI_STATE_ENABLED))) { + mWifiManager.setWifiEnabled(false); + Settings.Secure.putInt(cr, Settings.Secure.WIFI_SAVED_STATE, 1); + } + + if (mWifiManager.setWifiApEnabled(null, enable)) { + /* Disable here, enabled on receiving success broadcast */ + mCheckBox.setEnabled(false); + } else { + mCheckBox.setSummary(R.string.wifi_error); + } + + /** + * If needed, restore Wifi on tether disable + */ + if (!enable) { + int wifiSavedState = 0; + try { + wifiSavedState = Settings.Secure.getInt(cr, Settings.Secure.WIFI_SAVED_STATE); + } catch (Settings.SettingNotFoundException e) { + ; + } + if (wifiSavedState == 1) { + mWifiManager.setWifiEnabled(true); + Settings.Secure.putInt(cr, Settings.Secure.WIFI_SAVED_STATE, 0); + } + } + + return false; + } + + void updateConfigSummary(WifiConfiguration wifiConfig) { + String s = mContext.getString( + com.android.internal.R.string.wifi_tether_configure_ssid_default); + mCheckBox.setSummary(String.format( + mContext.getString(R.string.wifi_tether_enabled_subtext), + (wifiConfig == null) ? s : wifiConfig.SSID)); + } + + private void updateTetherState(Object[] available, Object[] tethered, Object[] errored) { + boolean wifiTethered = false; + boolean wifiErrored = false; + + for (Object o : tethered) { + String s = (String)o; + for (String regex : mWifiRegexs) { + if (s.matches(regex)) wifiTethered = true; + } + } + for (Object o: errored) { + String s = (String)o; + for (String regex : mWifiRegexs) { + if (s.matches(regex)) wifiErrored = true; + } + } + + if (wifiTethered) { + WifiConfiguration wifiConfig = mWifiManager.getWifiApConfiguration(); + updateConfigSummary(wifiConfig); + } else if (wifiErrored) { + mCheckBox.setSummary(R.string.wifi_error); + } + } + + private void handleWifiApStateChanged(int state) { + switch (state) { + case WifiManager.WIFI_AP_STATE_ENABLING: + mCheckBox.setSummary(R.string.wifi_starting); + mCheckBox.setEnabled(false); + break; + case WifiManager.WIFI_AP_STATE_ENABLED: + /** + * Summary on enable is handled by tether + * broadcast notice + */ + mCheckBox.setChecked(true); + /* Doesnt need the airplane check */ + mCheckBox.setEnabled(true); + break; + case WifiManager.WIFI_AP_STATE_DISABLING: + mCheckBox.setSummary(R.string.wifi_stopping); + mCheckBox.setEnabled(false); + break; + case WifiManager.WIFI_AP_STATE_DISABLED: + mCheckBox.setChecked(false); + mCheckBox.setSummary(mOriginalSummary); + enableWifiCheckBox(); + break; + default: + mCheckBox.setChecked(false); + mCheckBox.setSummary(R.string.wifi_error); + enableWifiCheckBox(); + } + } +} diff --git a/src/com/android/settings/wifi/WifiApSettings.java b/src/com/android/settings/wifi/WifiApSettings.java new file mode 100644 index 0000000..0815238 --- /dev/null +++ b/src/com/android/settings/wifi/WifiApSettings.java @@ -0,0 +1,138 @@ +/* + * 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.wifi; + +import com.android.settings.R; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.preference.CheckBoxPreference; +import android.provider.Settings; +import android.util.Log; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.AuthAlgorithm; +import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WifiManager; +import android.os.Bundle; + +/* + * Displays preferences for Tethering. + */ +public class WifiApSettings extends PreferenceActivity + implements DialogInterface.OnClickListener { + + private static final String WIFI_AP_SSID_AND_SECURITY = "wifi_ap_ssid_and_security"; + private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; + private static final int CONFIG_SUBTEXT = R.string.wifi_tether_configure_subtext; + + private static final int OPEN_INDEX = 0; + private static final int WPA_INDEX = 1; + + private static final int DIALOG_AP_SETTINGS = 1; + + private String[] mSecurityType; + private Preference mCreateNetwork; + private CheckBoxPreference mEnableWifiAp; + + private WifiApDialog mDialog; + private WifiManager mWifiManager; + private WifiApEnabler mWifiApEnabler; + private WifiConfiguration mWifiConfig = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + mWifiConfig = mWifiManager.getWifiApConfiguration(); + mSecurityType = getResources().getStringArray(R.array.wifi_ap_security); + + addPreferencesFromResource(R.xml.wifi_ap_settings); + + mCreateNetwork = findPreference(WIFI_AP_SSID_AND_SECURITY); + mEnableWifiAp = (CheckBoxPreference) findPreference(ENABLE_WIFI_AP); + + mWifiApEnabler = new WifiApEnabler(this, mEnableWifiAp); + + if(mWifiConfig == null) { + String s = getString(com.android.internal.R.string.wifi_tether_configure_ssid_default); + mCreateNetwork.setSummary(String.format(getString(CONFIG_SUBTEXT), + s, mSecurityType[OPEN_INDEX])); + } else { + mCreateNetwork.setSummary(String.format(getString(CONFIG_SUBTEXT), + mWifiConfig.SSID, + mWifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? + mSecurityType[WPA_INDEX] : mSecurityType[OPEN_INDEX])); + } + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id == DIALOG_AP_SETTINGS) { + mDialog = new WifiApDialog(this, this, mWifiConfig); + return mDialog; + } + return null; + } + + @Override + protected void onResume() { + super.onResume(); + mWifiApEnabler.resume(); + } + + @Override + protected void onPause() { + super.onPause(); + mWifiApEnabler.pause(); + } + + @Override + public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { + if (preference == mCreateNetwork) { + showDialog(DIALOG_AP_SETTINGS); + } + return true; + } + + public void onClick(DialogInterface dialogInterface, int button) { + + if (button == DialogInterface.BUTTON_POSITIVE) { + mWifiConfig = mDialog.getConfig(); + if(mWifiConfig != null) { + mWifiManager.setWifiApEnabled(mWifiConfig, true); + mCreateNetwork.setSummary(String.format(getString(CONFIG_SUBTEXT), + mWifiConfig.SSID, + mWifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? + mSecurityType[WPA_INDEX] : mSecurityType[OPEN_INDEX])); + /** + * There is no tether notification on changing AP + * configuration. Update status with new config. + */ + mWifiApEnabler.updateConfigSummary(mWifiConfig); + + } + } + } +} diff --git a/src/com/android/settings/wifi/WifiConfigInfo.java b/src/com/android/settings/wifi/WifiConfigInfo.java new file mode 100644 index 0000000..2ed4f02 --- /dev/null +++ b/src/com/android/settings/wifi/WifiConfigInfo.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 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.app.Activity; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.widget.TextView; +import android.net.wifi.WifiConfiguration; +import java.util.List; + +import com.android.settings.R; + + +/** + * Configuration details saved by the user on the WifiSettings screen + */ +public class WifiConfigInfo extends Activity { + + private static final String TAG = "WifiConfigInfo"; + + private TextView mConfigList; + private WifiManager mWifiManager; + + //============================ + // Activity lifecycle + //============================ + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE); + setContentView(R.layout.wifi_config_info); + mConfigList = (TextView) findViewById(R.id.config_list); + } + + @Override + protected void onResume() { + super.onResume(); + final List<WifiConfiguration> wifiConfigs = mWifiManager.getConfiguredNetworks(); + StringBuffer configList = new StringBuffer(); + for (int i = wifiConfigs.size() - 1; i >= 0; i--) { + configList.append(wifiConfigs.get(i)); + } + mConfigList.setText(configList); + } + +} diff --git a/src/com/android/settings/wifi/WifiDialog.java b/src/com/android/settings/wifi/WifiDialog.java new file mode 100644 index 0000000..a8bf717 --- /dev/null +++ b/src/com/android/settings/wifi/WifiDialog.java @@ -0,0 +1,370 @@ +/* + * 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.wifi; + +import com.android.settings.R; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.net.NetworkInfo.DetailedState; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.AuthAlgorithm; +import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WifiInfo; +import android.os.Bundle; +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.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.Spinner; +import android.widget.TextView; + +class WifiDialog extends AlertDialog implements View.OnClickListener, + TextWatcher, AdapterView.OnItemSelectedListener { + private static final String KEYSTORE_SPACE = "keystore://"; + + static final int BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE; + static final int BUTTON_FORGET = DialogInterface.BUTTON_NEUTRAL; + + final boolean edit; + private final DialogInterface.OnClickListener mListener; + private final AccessPoint mAccessPoint; + + private View mView; + private TextView mSsid; + private int mSecurity; + private TextView mPassword; + + private Spinner mEapMethod; + private Spinner mEapCaCert; + private Spinner mPhase2; + private Spinner mEapUserCert; + private TextView mEapIdentity; + private TextView mEapAnonymous; + + static boolean requireKeyStore(WifiConfiguration config) { + String values[] = {config.ca_cert.value(), config.client_cert.value(), + config.private_key.value()}; + for (String value : values) { + if (value != null && value.startsWith(KEYSTORE_SPACE)) { + return true; + } + } + return false; + } + + WifiDialog(Context context, DialogInterface.OnClickListener listener, + AccessPoint accessPoint, boolean edit) { + super(context); + this.edit = edit; + mListener = listener; + mAccessPoint = accessPoint; + mSecurity = (accessPoint == null) ? AccessPoint.SECURITY_NONE : accessPoint.security; + } + + WifiConfiguration getConfig() { + if (mAccessPoint != null && mAccessPoint.networkId != -1 && !edit) { + return null; + } + + WifiConfiguration config = new WifiConfiguration(); + + if (mAccessPoint == null) { + config.SSID = AccessPoint.convertToQuotedString( + mSsid.getText().toString()); + // If the user adds a network manually, assume that it is hidden. + config.hiddenSSID = true; + } else if (mAccessPoint.networkId == -1) { + config.SSID = AccessPoint.convertToQuotedString( + mAccessPoint.ssid); + } else { + config.networkId = mAccessPoint.networkId; + } + + switch (mSecurity) { + case AccessPoint.SECURITY_NONE: + config.allowedKeyManagement.set(KeyMgmt.NONE); + return config; + + case AccessPoint.SECURITY_WEP: + config.allowedKeyManagement.set(KeyMgmt.NONE); + config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); + config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED); + if (mPassword.length() != 0) { + int length = mPassword.length(); + String password = mPassword.getText().toString(); + // WEP-40, WEP-104, and 256-bit WEP (WEP-232?) + if ((length == 10 || length == 26 || length == 58) && + password.matches("[0-9A-Fa-f]*")) { + config.wepKeys[0] = password; + } else { + config.wepKeys[0] = '"' + password + '"'; + } + } + return config; + + case AccessPoint.SECURITY_PSK: + config.allowedKeyManagement.set(KeyMgmt.WPA_PSK); + if (mPassword.length() != 0) { + String password = mPassword.getText().toString(); + if (password.matches("[0-9A-Fa-f]{64}")) { + config.preSharedKey = password; + } else { + config.preSharedKey = '"' + password + '"'; + } + } + return config; + + case AccessPoint.SECURITY_EAP: + config.allowedKeyManagement.set(KeyMgmt.WPA_EAP); + config.allowedKeyManagement.set(KeyMgmt.IEEE8021X); + config.eap.setValue((String) mEapMethod.getSelectedItem()); + + config.phase2.setValue((mPhase2.getSelectedItemPosition() == 0) ? "" : + "auth=" + mPhase2.getSelectedItem()); + config.ca_cert.setValue((mEapCaCert.getSelectedItemPosition() == 0) ? "" : + KEYSTORE_SPACE + Credentials.CA_CERTIFICATE + + (String) mEapCaCert.getSelectedItem()); + config.client_cert.setValue((mEapUserCert.getSelectedItemPosition() == 0) ? "" : + KEYSTORE_SPACE + Credentials.USER_CERTIFICATE + + (String) mEapUserCert.getSelectedItem()); + config.private_key.setValue((mEapUserCert.getSelectedItemPosition() == 0) ? "" : + KEYSTORE_SPACE + Credentials.USER_PRIVATE_KEY + + (String) mEapUserCert.getSelectedItem()); + config.identity.setValue((mEapIdentity.length() == 0) ? "" : + mEapIdentity.getText().toString()); + config.anonymous_identity.setValue((mEapAnonymous.length() == 0) ? "" : + mEapAnonymous.getText().toString()); + if (mPassword.length() != 0) { + config.password.setValue(mPassword.getText().toString()); + } + return config; + } + return null; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + mView = getLayoutInflater().inflate(R.layout.wifi_dialog, null); + setView(mView); + setInverseBackgroundForced(true); + + Context context = getContext(); + Resources resources = context.getResources(); + + if (mAccessPoint == null) { + setTitle(R.string.wifi_add_network); + mView.findViewById(R.id.type).setVisibility(View.VISIBLE); + mSsid = (TextView) mView.findViewById(R.id.ssid); + mSsid.addTextChangedListener(this); + ((Spinner) mView.findViewById(R.id.security)).setOnItemSelectedListener(this); + setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_save), mListener); + } else { + setTitle(mAccessPoint.ssid); + ViewGroup group = (ViewGroup) mView.findViewById(R.id.info); + + DetailedState state = mAccessPoint.getState(); + if (state != null) { + addRow(group, R.string.wifi_status, Summary.get(getContext(), state)); + } + + String[] type = resources.getStringArray(R.array.wifi_security); + addRow(group, R.string.wifi_security, type[mAccessPoint.security]); + + int level = mAccessPoint.getLevel(); + if (level != -1) { + String[] signal = resources.getStringArray(R.array.wifi_signal); + addRow(group, R.string.wifi_signal, signal[level]); + } + + WifiInfo info = mAccessPoint.getInfo(); + if (info != null) { + addRow(group, R.string.wifi_speed, info.getLinkSpeed() + WifiInfo.LINK_SPEED_UNITS); + // TODO: fix the ip address for IPv6. + int address = info.getIpAddress(); + if (address != 0) { + addRow(group, R.string.wifi_ip_address, Formatter.formatIpAddress(address)); + } + } + + if (mAccessPoint.networkId == -1 || edit) { + showSecurityFields(); + } + + if (edit) { + setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_save), mListener); + } else { + if (state == null && level != -1) { + setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_connect), mListener); + } + if (mAccessPoint.networkId != -1) { + setButton(BUTTON_FORGET, context.getString(R.string.wifi_forget), mListener); + } + } + } + + setButton(DialogInterface.BUTTON_NEGATIVE, + context.getString(R.string.wifi_cancel), mListener); + + super.onCreate(savedInstanceState); + + if (getButton(BUTTON_SUBMIT) != null) { + validate(); + } + } + + private void addRow(ViewGroup group, int name, String value) { + View row = getLayoutInflater().inflate(R.layout.wifi_dialog_row, group, false); + ((TextView) row.findViewById(R.id.name)).setText(name); + ((TextView) row.findViewById(R.id.value)).setText(value); + group.addView(row); + } + + private void validate() { + // TODO: make sure this is complete. + if ((mSsid != null && mSsid.length() == 0) || + ((mAccessPoint == null || mAccessPoint.networkId == -1) && + ((mSecurity == AccessPoint.SECURITY_WEP && mPassword.length() == 0) || + (mSecurity == AccessPoint.SECURITY_PSK && mPassword.length() < 8)))) { + getButton(BUTTON_SUBMIT).setEnabled(false); + } else { + getButton(BUTTON_SUBMIT).setEnabled(true); + } + } + + public void onClick(View view) { + mPassword.setInputType( + InputType.TYPE_CLASS_TEXT | (((CheckBox) view).isChecked() ? + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : + InputType.TYPE_TEXT_VARIATION_PASSWORD)); + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void afterTextChanged(Editable editable) { + validate(); + } + + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mSecurity = position; + showSecurityFields(); + validate(); + } + + public void onNothingSelected(AdapterView parent) { + } + + private void showSecurityFields() { + if (mSecurity == AccessPoint.SECURITY_NONE) { + mView.findViewById(R.id.fields).setVisibility(View.GONE); + return; + } + mView.findViewById(R.id.fields).setVisibility(View.VISIBLE); + + if (mPassword == null) { + mPassword = (TextView) mView.findViewById(R.id.password); + mPassword.addTextChangedListener(this); + ((CheckBox) mView.findViewById(R.id.show_password)).setOnClickListener(this); + + if (mAccessPoint != null && mAccessPoint.networkId != -1) { + mPassword.setHint(R.string.wifi_unchanged); + } + } + + if (mSecurity != AccessPoint.SECURITY_EAP) { + mView.findViewById(R.id.eap).setVisibility(View.GONE); + return; + } + mView.findViewById(R.id.eap).setVisibility(View.VISIBLE); + + if (mEapMethod == null) { + mEapMethod = (Spinner) mView.findViewById(R.id.method); + mPhase2 = (Spinner) mView.findViewById(R.id.phase2); + mEapCaCert = (Spinner) mView.findViewById(R.id.ca_cert); + mEapUserCert = (Spinner) mView.findViewById(R.id.user_cert); + mEapIdentity = (TextView) mView.findViewById(R.id.identity); + mEapAnonymous = (TextView) mView.findViewById(R.id.anonymous); + + loadCertificates(mEapCaCert, Credentials.CA_CERTIFICATE); + loadCertificates(mEapUserCert, Credentials.USER_PRIVATE_KEY); + + if (mAccessPoint != null && mAccessPoint.networkId != -1) { + WifiConfiguration config = mAccessPoint.getConfig(); + setSelection(mEapMethod, config.eap.value()); + setSelection(mPhase2, config.phase2.value()); + setCertificate(mEapCaCert, Credentials.CA_CERTIFICATE, + config.ca_cert.value()); + setCertificate(mEapUserCert, Credentials.USER_PRIVATE_KEY, + config.private_key.value()); + mEapIdentity.setText(config.identity.value()); + mEapAnonymous.setText(config.anonymous_identity.value()); + } + } + } + + private void loadCertificates(Spinner spinner, String prefix) { + String[] certs = KeyStore.getInstance().saw(prefix); + Context context = getContext(); + String unspecified = context.getString(R.string.wifi_unspecified); + + if (certs == null || certs.length == 0) { + certs = new String[] {unspecified}; + } else { + String[] array = new String[certs.length + 1]; + array[0] = unspecified; + System.arraycopy(certs, 0, array, 1, certs.length); + certs = array; + } + + ArrayAdapter<String> adapter = new ArrayAdapter<String>( + context, android.R.layout.simple_spinner_item, certs); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + } + + private void setCertificate(Spinner spinner, String prefix, String cert) { + prefix = KEYSTORE_SPACE + prefix; + if (cert != null && cert.startsWith(prefix)) { + setSelection(spinner, cert.substring(prefix.length())); + } + } + + private void setSelection(Spinner spinner, String value) { + if (value != null) { + ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinner.getAdapter(); + for (int i = adapter.getCount() - 1; i >= 0; --i) { + if (value.equals(adapter.getItem(i))) { + spinner.setSelection(i); + break; + } + } + } + } +} diff --git a/src/com/android/settings/wifi/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java index 10a672b..ef9f346 100644 --- a/src/com/android/settings/wifi/WifiEnabler.java +++ b/src/com/android/settings/wifi/WifiEnabler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * 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. @@ -16,174 +16,135 @@ package com.android.settings.wifi; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; -import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; - import com.android.settings.R; -import com.android.settings.AirplaneModeEnabler; +import com.android.settings.WirelessSettings; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; 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.util.Config; -import android.util.Log; +import android.widget.Toast; public class WifiEnabler implements Preference.OnPreferenceChangeListener { - - private static final boolean LOCAL_LOGD = Config.LOGD || WifiLayer.LOGV; - private static final String TAG = "SettingsWifiEnabler"; - private final Context mContext; - private final WifiManager mWifiManager; - private final CheckBoxPreference mWifiCheckBoxPref; + private final CheckBoxPreference mCheckBox; private final CharSequence mOriginalSummary; - - private final IntentFilter mWifiStateFilter; - private final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { + private final WifiManager mWifiManager; + private final IntentFilter mIntentFilter; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - handleWifiStateChanged( - intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WIFI_STATE_UNKNOWN), - intent.getIntExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, - WIFI_STATE_UNKNOWN)); - } else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - handleNetworkStateChanged( - (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)); + String action = intent.getAction(); + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { + handleWifiStateChanged(intent.getIntExtra( + WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); + } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) { + handleStateChanged(WifiInfo.getDetailedStateOf((SupplicantState) + intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE))); + } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { + handleStateChanged(((NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO)).getDetailedState()); } } }; - - public WifiEnabler(Context context, WifiManager wifiManager, - CheckBoxPreference wifiCheckBoxPreference) { + + public WifiEnabler(Context context, CheckBoxPreference checkBox) { mContext = context; - mWifiCheckBoxPref = wifiCheckBoxPreference; - mWifiManager = wifiManager; - - mOriginalSummary = wifiCheckBoxPreference.getSummary(); - wifiCheckBoxPreference.setPersistent(false); - - mWifiStateFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); - mWifiStateFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mCheckBox = checkBox; + mOriginalSummary = checkBox.getSummary(); + checkBox.setPersistent(false); + + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); + // The order matters! We really should not depend on this. :( + mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); } public void resume() { - int state = mWifiManager.getWifiState(); - // This is the widget enabled state, not the preference toggled state - mWifiCheckBoxPref.setEnabled(state == WIFI_STATE_ENABLED || state == WIFI_STATE_DISABLED - || state == WIFI_STATE_UNKNOWN); - - mContext.registerReceiver(mWifiStateReceiver, mWifiStateFilter); - mWifiCheckBoxPref.setOnPreferenceChangeListener(this); + // Wi-Fi state is sticky, so just let the receiver update UI + mContext.registerReceiver(mReceiver, mIntentFilter); + mCheckBox.setOnPreferenceChangeListener(this); } public void pause() { - mContext.unregisterReceiver(mWifiStateReceiver); - mWifiCheckBoxPref.setOnPreferenceChangeListener(null); + mContext.unregisterReceiver(mReceiver); + mCheckBox.setOnPreferenceChangeListener(null); } public boolean onPreferenceChange(Preference preference, Object value) { - // Turn on/off Wi-Fi - setWifiEnabled((Boolean) value); - - // Don't update UI to opposite state until we're sure - return false; - } + boolean enable = (Boolean) value; - private void setWifiEnabled(final boolean enable) { - // Disable button - mWifiCheckBoxPref.setEnabled(false); - - if (!mWifiManager.setWifiEnabled(enable)) { - mWifiCheckBoxPref.setSummary(enable ? R.string.error_starting : R.string.error_stopping); + // 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; } - } - - private void handleWifiStateChanged(int wifiState, int previousWifiState) { - if (LOCAL_LOGD) { - Log.d(TAG, "Received wifi state changed from " - + getHumanReadableWifiState(previousWifiState) + " to " - + getHumanReadableWifiState(wifiState)); + /** + * Disable tethering if enabling Wifi + */ + int wifiApState = mWifiManager.getWifiApState(); + if (enable && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || + (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { + mWifiManager.setWifiApEnabled(null, false); } - - if (wifiState == WIFI_STATE_DISABLED || wifiState == WIFI_STATE_ENABLED) { - mWifiCheckBoxPref.setChecked(wifiState == WIFI_STATE_ENABLED); - mWifiCheckBoxPref - .setSummary(wifiState == WIFI_STATE_DISABLED ? mOriginalSummary : null); - - mWifiCheckBoxPref.setEnabled(isWifiAllowed(mContext)); - - } else if (wifiState == WIFI_STATE_DISABLING || wifiState == WIFI_STATE_ENABLING) { - mWifiCheckBoxPref.setSummary(wifiState == WIFI_STATE_ENABLING ? R.string.wifi_starting - : R.string.wifi_stopping); - - } else if (wifiState == WIFI_STATE_UNKNOWN) { - int message = R.string.wifi_error; - if (previousWifiState == WIFI_STATE_ENABLING) message = R.string.error_starting; - else if (previousWifiState == WIFI_STATE_DISABLING) message = R.string.error_stopping; - - mWifiCheckBoxPref.setChecked(false); - mWifiCheckBoxPref.setSummary(message); - mWifiCheckBoxPref.setEnabled(true); + if (mWifiManager.setWifiEnabled(enable)) { + mCheckBox.setEnabled(false); + } else { + mCheckBox.setSummary(R.string.wifi_error); } - } - private void handleNetworkStateChanged(NetworkInfo networkInfo) { - - if (LOCAL_LOGD) { - Log.d(TAG, "Received network state changed to " + networkInfo); - } - - if (mWifiManager.isWifiEnabled()) { - String summary = WifiStatus.getStatus(mContext, - mWifiManager.getConnectionInfo().getSSID(), networkInfo.getDetailedState()); - mWifiCheckBoxPref.setSummary(summary); - } + // Don't update UI to opposite state until we're sure + return false; } - - private static boolean isWifiAllowed(Context context) { - // allowed if we are not in airplane mode - if (!AirplaneModeEnabler.isAirplaneModeOn(context)) { - return true; - } - // allowed if wifi is not in AIRPLANE_MODE_RADIOS - String radios = Settings.System.getString(context.getContentResolver(), - Settings.System.AIRPLANE_MODE_RADIOS); - if (radios == null || !radios.contains(Settings.System.RADIO_WIFI)) { - return true; + + private void handleWifiStateChanged(int state) { + switch (state) { + case WifiManager.WIFI_STATE_ENABLING: + mCheckBox.setSummary(R.string.wifi_starting); + mCheckBox.setEnabled(false); + break; + case WifiManager.WIFI_STATE_ENABLED: + mCheckBox.setChecked(true); + mCheckBox.setSummary(null); + mCheckBox.setEnabled(true); + break; + case WifiManager.WIFI_STATE_DISABLING: + mCheckBox.setSummary(R.string.wifi_stopping); + mCheckBox.setEnabled(false); + break; + case WifiManager.WIFI_STATE_DISABLED: + mCheckBox.setChecked(false); + mCheckBox.setSummary(mOriginalSummary); + mCheckBox.setEnabled(true); + break; + default: + mCheckBox.setChecked(false); + mCheckBox.setSummary(R.string.wifi_error); + mCheckBox.setEnabled(true); } - // allowed if wifi is in AIRPLANE_MODE_TOGGLEABLE_RADIOS - radios = Settings.System.getString(context.getContentResolver(), - Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); - return (radios != null && radios.contains(Settings.System.RADIO_WIFI)); } - private static String getHumanReadableWifiState(int wifiState) { - switch (wifiState) { - case WIFI_STATE_DISABLED: - return "Disabled"; - case WIFI_STATE_DISABLING: - return "Disabling"; - case WIFI_STATE_ENABLED: - return "Enabled"; - case WIFI_STATE_ENABLING: - return "Enabling"; - case WIFI_STATE_UNKNOWN: - return "Unknown"; - default: - return "Some other state!"; + private void handleStateChanged(NetworkInfo.DetailedState state) { + // 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()) { + WifiInfo info = mWifiManager.getConnectionInfo(); + if (info != null) { + mCheckBox.setSummary(Summary.get(mContext, info.getSSID(), state)); + } } } } diff --git a/src/com/android/settings/wifi/WifiInfo.java b/src/com/android/settings/wifi/WifiInfo.java new file mode 100644 index 0000000..291a495 --- /dev/null +++ b/src/com/android/settings/wifi/WifiInfo.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009 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 com.android.settings.R; + +import android.os.Bundle; +import android.preference.PreferenceActivity; + + +/** + * Wifi information menu item on the diagnostic screen + */ +public class WifiInfo extends PreferenceActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.testing_wifi_settings); + } + +} diff --git a/src/com/android/settings/wifi/WifiLayer.java b/src/com/android/settings/wifi/WifiLayer.java deleted file mode 100644 index ce518e1..0000000 --- a/src/com/android/settings/wifi/WifiLayer.java +++ /dev/null @@ -1,1316 +0,0 @@ -/* - * Copyright (C) 2007 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 static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; - -import com.android.settings.R; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; -import android.net.wifi.ScanResult; -import android.net.wifi.SupplicantState; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.os.Handler; -import android.os.Message; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Config; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Helper class for abstracting Wi-Fi. - * <p> - * Client must call {@link #onCreate()}, {@link #onCreatedCallback()}, - * {@link #onPause()}, {@link #onResume()}. - */ -public class WifiLayer { - - private static final String TAG = "SettingsWifiLayer"; - static final boolean LOGV = false || Config.LOGV; - - //============================ - // Other member variables - //============================ - - private Context mContext; - private Callback mCallback; - - static final int MESSAGE_ATTEMPT_SCAN = 1; - private Handler mHandler = new MyHandler(); - - //============================ - // Wifi member variables - //============================ - - private WifiManager mWifiManager; - private IntentFilter mIntentFilter; - private List<AccessPointState> mApScanList = new ArrayList<AccessPointState>(); - private List<AccessPointState> mApOtherList = new ArrayList<AccessPointState>(); - private AccessPointState mCurrentPrimaryAp; - - /** The last access point that we were authenticating with. */ - private AccessPointState mLastAuthenticatingAp; - - /** The delay between scans when we're continually scanning. */ - private static final int CONTINUOUS_SCAN_DELAY_MS = 6000; - /** On failure, the maximum retries for scanning. */ - private static final int SCAN_MAX_RETRY = 5; - /** On failure, the delay between each scan retry. */ - private static final int SCAN_RETRY_DELAY_MS = 1000; - /** On failure, the number of retries so far. */ - private int mScanRetryCount = 0; - /** - * Whether we're currently obtaining an address. Continuous scanning will be - * disabled in this state. - */ - private boolean mIsObtainingAddress; - - /** - * See {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}. - */ - private int WIFI_NUM_OPEN_NETWORKS_KEPT; - /** - * Once the highest priority exceeds this value, all networks will be - * wrapped around starting at 0. This is so another client of the Wi-Fi - * API can have access points that aren't managed by us. (If the other - * client wants lower-priority access points than ours, it can use negative - * priority.) - */ - private static final int HIGHEST_PRIORITY_MAX_VALUE = 99999; - /** - * Never access directly, only the related methods should. - */ - private int mNextHighestPriority; - - /** - * This is used to track when the user wants to connect to a specific AP. We - * disable all other APs, set this to true, and let wpa_supplicant connect. - * Once we get a network state change, we re-enable the rest of them. - */ - private boolean mReenableApsOnNetworkStateChange = false; - - /** - * The current supplicant state, as broadcasted. - */ - private SupplicantState mCurrentSupplicantState; - - //============================ - // Inner classes - //============================ - - interface Callback { - void onError(int messageResId); - - /** - * Called when an AP is added or removed. - * - * @param ap The AP. - * @param added {@code true} if added, {@code false} if removed. - */ - void onAccessPointSetChanged(AccessPointState ap, boolean added); - - /** - * Called when the scanning status changes. - * - * @param started {@code true} if the scanning just started, - * {@code false} if it just ended. - */ - void onScanningStatusChanged(boolean started); - - /** - * Called when the access points should be enabled or disabled. This is - * called from both wpa_supplicant being connected/disconnected and Wi-Fi - * being enabled/disabled. - * - * @param enabled {@code true} if they should be enabled, {@code false} - * if they should be disabled. - */ - void onAccessPointsStateChanged(boolean enabled); - - /** - * Called when there is trouble authenticating and the retry-password - * dialog should be shown. - * - * @param ap The access point. - */ - void onRetryPassword(AccessPointState ap); - } - - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - handleNetworkStateChanged( - (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO), - intent.getStringExtra(WifiManager.EXTRA_BSSID)); - } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { - handleScanResultsAvailable(); - } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) { - handleSupplicantConnectionChanged( - intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false)); - } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { - handleSupplicantStateChanged( - (SupplicantState) intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE), - intent.hasExtra(WifiManager.EXTRA_SUPPLICANT_ERROR), - intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0)); - } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN)); - } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - handleSignalChanged(intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, 0)); - } else if (action.equals(WifiManager.NETWORK_IDS_CHANGED_ACTION)) { - handleNetworkIdsChanged(); - } - } - }; - - /** - * If using this class, make sure to call the callbacks of this class, such - * as {@link #onCreate()}, {@link #onCreatedCallback()}, - * {@link #onPause()}, {@link #onResume()}. - * - * @param context The context. - * @param callback The interface that will be invoked when events from this - * class are generated. - */ - public WifiLayer(Context context, Callback callback) { - mContext = context; - mCallback = callback; - } - - //============================ - // Lifecycle - //============================ - - /** - * The client MUST call this. - * <p> - * This shouldn't have any dependency on the callback. - */ - public void onCreate() { - mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - - mIntentFilter = new IntentFilter(); - mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); - mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); - mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); - mIntentFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); - - WIFI_NUM_OPEN_NETWORKS_KEPT = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, 10); - } - - /** - * The client MUST call this. - * <p> - * Callback is ready, this can do whatever it wants with it. - */ - public void onCreatedCallback() { - if (isWifiEnabled()) { - refreshAll(false); - } - } - - /** - * The client MUST call this. - * - * @see android.app.Activity#onResume - */ - public void onResume() { - mContext.registerReceiver(mReceiver, mIntentFilter); - - if (isWifiEnabled()) { - // Kick start the continual scan - queueContinuousScan(); - } - } - - /** - * The client MUST call this. - * - * @see android.app.Activity#onPause - */ - public void onPause() { - mContext.unregisterReceiver(mReceiver); - - attemptReenableAllAps(); - - removeFutureScans(); - } - - //============================ - // "Public" API - //============================ - - /** - * Returns an AccessPointState instance (that we track locally in WifiLayer) - * for the given state. First, we check if we track the given instance. If - * not, we find an equal AccessPointState instance that we track. - * - * @param state An AccessPointState instance that does not necessarily have - * to be one that this WifiLayer class tracks. For example, it - * could be the result of unparceling. - * @return An AccessPointState instance that this WifiLayer class tracks. - */ - public AccessPointState getWifiLayerApInstance(AccessPointState state) { - synchronized (this) { - - if (hasApInstanceLocked(state)) { - return state; - } - - return findApLocked(state.networkId, state.bssid, state.ssid, state.security); - } - } - - /** - * Connects to the network, and creates the Wi-Fi API config if necessary. - * - * @param state The state of the network to connect to. This MUST be an - * instance that was given to you by this class. If you - * constructed the instance yourself (for example, after - * unparceling it), you should use - * {@link #getWifiLayerApInstance(AccessPointState)}. - * @return Whether the operation was successful. - */ - public boolean connectToNetwork(AccessPointState state) { - if (LOGV) { - Log.v(TAG, "Connecting to " + state); - } - - // Need WifiConfiguration for the AP - WifiConfiguration config = findConfiguredNetwork(state); - - if (LOGV) { - Log.v(TAG, " Found configured network " + config); - } - - if (config == null) { - /* - * Connecting for the first time, need to create it. We will enable - * and save it below (when we set priority). - */ - config = addConfiguration(state, 0); - - if (config == null) { - Log.e(TAG, "Config is still null, even after attempting to add it."); - error(R.string.error_connecting); - return false; - } - - /* - * We could reload the configured networks, but instead just - * shortcut and add this state to our list in memory. - */ - ensureTrackingState(state); - } else { - // Make sure the configuration has the latest from the state - state.updateWifiConfiguration(config); - } - - // Enable this network before we save to storage - if (!managerEnableNetwork(state, false)) { - Log.e(TAG, "Could not enable network ID " + state.networkId); - error(R.string.error_connecting); - return false; - } - - /* - * Give it highest priority, this could cause a network ID change, so do - * it after any modifications to the network we're connecting to - */ - setHighestPriorityStateAndSave(state, config); - - /* - * We force supplicant to connect to this network by disabling the - * others. We do this AFTER we save above so this disabled flag isn't - * persisted. - */ - mReenableApsOnNetworkStateChange = true; - if (!managerEnableNetwork(state, true)) { - Log.e(TAG, "Could not enable network ID " + state.networkId); - error(R.string.error_connecting); - return false; - } - - if (LOGV) { - Log.v(TAG, " Enabled network " + state.networkId); - } - - if (mCurrentSupplicantState == SupplicantState.DISCONNECTED || - mCurrentSupplicantState == SupplicantState.SCANNING) { - mWifiManager.reconnect(); - } - - // Check for too many configured open networks - if (!state.hasSecurity()) { - checkForExcessOpenNetworks(); - } - - return true; - } - - /** - * Saves a network, and creates the Wi-Fi API config if necessary. - * - * @param state The state of the network to save. If you constructed the - * instance yourself (for example, after unparceling it), you - * should use {@link #getWifiLayerApInstance(AccessPointState)}. - * @return Whether the operation was successful. - */ - public boolean saveNetwork(AccessPointState state) { - WifiConfiguration config = findConfiguredNetwork(state); - - if (config == null) { - // if the user is adding a new network, assume that it is hidden - state.setHiddenSsid(true); - - config = addConfiguration(state, ADD_CONFIGURATION_ENABLE); - - if (config == null) { - Log.e(TAG, "Could not save configuration, call to addConfiguration failed."); - error(R.string.error_saving); - return false; - } - - } else { - state.updateWifiConfiguration(config); - if (mWifiManager.updateNetwork(config) == -1) { - Log.e(TAG, "Could not update configuration, call to WifiManager failed."); - error(R.string.error_saving); - return false; - } - } - - // Successfully added network, go ahead and persist - if (!managerSaveConfiguration()) { - Log.e(TAG, "Could not save configuration, call to WifiManager failed."); - error(R.string.error_saving); - return false; - } - - /* - * It's necessary to update the network id of this state because the network id - * could have changed after the configuration is saved. For example, if there are - * more than 10 saved open-networks, some older open-networks will have been be forgotten. - */ - state.setNetworkId(AccessPointState.NETWORK_ID_ANY); - config = findConfiguredNetwork(state); - if (config != null) { - state.setNetworkId(config.networkId); - } - - /* - * We could reload the configured networks, but instead just shortcut - * and add this state to our list in memory - */ - ensureTrackingState(state); - - return true; - } - - /** - * Forgets a network. - * - * @param state The state of the network to forget. If you constructed the - * instance yourself (for example, after unparceling it), you - * should use {@link #getWifiLayerApInstance(AccessPointState)}. - * @return Whether the operation was succesful. - */ - public boolean forgetNetwork(AccessPointState state) { - if (!state.configured) { - Log.w(TAG, "Inconsistent state: Forgetting a network that is not configured."); - return true; - } - - int oldNetworkId = state.networkId; - state.forget(); - - if (!state.seen) { - // If it is not seen, it should be removed from the UI - removeApFromUi(state); - } - - synchronized (this) { - mApOtherList.remove(state); - // It should not be removed from the scan list, since if it was - // there that means it's still seen - } - - if (!mWifiManager.removeNetwork(oldNetworkId)) { - Log.e(TAG, "Removing network " + state.ssid + " (network ID " + oldNetworkId + - ") failed."); - return false; - } - - if (!managerSaveConfiguration()) { - error(R.string.error_saving); - return false; - } - - return true; - } - - /** - * This ensures this class is tracking the given state. This means it is in - * our list of access points, either in the scanned list or in the - * remembered list. - * - * @param state The state that will be checked for tracking, and if not - * tracking will be added to the remembered list in memory. - */ - private void ensureTrackingState(AccessPointState state) { - synchronized (this) { - if (hasApInstanceLocked(state)) { - return; - } - - mApOtherList.add(state); - } - } - - /** - * Attempts to scan networks. This has a retry mechanism. - */ - public void attemptScan() { - - // Remove any future scans since we're scanning right now - removeFutureScans(); - - if (!mWifiManager.isWifiEnabled()) return; - - if (!mWifiManager.startScanActive()) { - postAttemptScan(); - } else { - mScanRetryCount = 0; - } - } - - private void queueContinuousScan() { - mHandler.removeMessages(MESSAGE_ATTEMPT_SCAN); - - if (!mIsObtainingAddress) { - // Don't do continuous scan while in obtaining IP state - mHandler.sendEmptyMessageDelayed(MESSAGE_ATTEMPT_SCAN, CONTINUOUS_SCAN_DELAY_MS); - } - } - - private void removeFutureScans() { - mHandler.removeMessages(MESSAGE_ATTEMPT_SCAN); - } - - public boolean isWifiEnabled() { - return mWifiManager.isWifiEnabled(); - } - - public void error(int messageResId) { - Log.e(TAG, mContext.getResources().getString(messageResId)); - - if (mCallback != null) { - mCallback.onError(messageResId); - } - } - - //============================ - // Wifi logic - //============================ - - private void refreshAll(boolean attemptScan) { - loadConfiguredAccessPoints(); - refreshStatus(); - - if (attemptScan) { - attemptScan(); - } - } - - private void postAttemptScan() { - onScanningStarted(); - - if (++mScanRetryCount < SCAN_MAX_RETRY) { - // Just in case, remove previous ones first - removeFutureScans(); - mHandler.sendEmptyMessageDelayed(MESSAGE_ATTEMPT_SCAN, SCAN_RETRY_DELAY_MS); - } else { - // Show an error once we run out of attempts - error(R.string.error_scanning); - onScanningEnded(); - } - } - - private void onScanningStarted() { - if (mCallback != null) { - mCallback.onScanningStatusChanged(true); - } - } - - private void onScanningEnded() { - queueContinuousScan(); - - if (mCallback != null) { - mCallback.onScanningStatusChanged(false); - } - } - - private void clearApLists() { - List<AccessPointState> accessPoints = new ArrayList<AccessPointState>(); - - synchronized(this) { - // Clear the logic's list of access points - accessPoints.addAll(mApScanList); - accessPoints.addAll(mApOtherList); - mApScanList.clear(); - mApOtherList.clear(); - } - - for (int i = accessPoints.size() - 1; i >= 0; i--) { - removeApFromUi(accessPoints.get(i)); - } - } - - private boolean managerSaveConfiguration() { - boolean retValue = mWifiManager.saveConfiguration(); - - /* - * We need to assume the network IDs have changed, so handle this. Note: - * we also have a receiver on the broadcast intent in case another wifi - * framework client caused the change. In this case, we will handle the - * possible network ID change twice (but it's not too costly). - */ - handleNetworkIdsChanged(); - - return retValue; - } - - private boolean managerEnableNetwork(AccessPointState state, boolean disableOthers) { - if (!mWifiManager.enableNetwork(state.networkId, disableOthers)) { - return false; - } - - // Enabling was successful, make sure the state is not disabled - state.setDisabled(false); - - return true; - } - - private static final int ADD_CONFIGURATION_ENABLE = 1; - private static final int ADD_CONFIGURATION_SAVE = 2; - private WifiConfiguration addConfiguration(AccessPointState state, int flags) { - // Create and add - WifiConfiguration config = new WifiConfiguration(); - - state.updateWifiConfiguration(config); - - final int networkId = mWifiManager.addNetwork(config); - if (networkId == -1) { - return null; - } - - state.setNetworkId(networkId); - state.setConfigured(true); - - // If we should, then enable it, since it comes disabled by default - if ((flags & ADD_CONFIGURATION_ENABLE) != 0 - && !managerEnableNetwork(state, false)) { - return null; - } - - // If we should, then save it - if ((flags & ADD_CONFIGURATION_SAVE) != 0 && !managerSaveConfiguration()) { - return null; - } - - if (mCallback != null) { - mCallback.onAccessPointSetChanged(state, true); - } - - return config; - } - - private WifiConfiguration findConfiguredNetwork(AccessPointState state) { - final List<WifiConfiguration> wifiConfigs = getConfiguredNetworks(); - - for (int i = wifiConfigs.size() - 1; i >= 0; i--) { - final WifiConfiguration wifiConfig = wifiConfigs.get(i); - if (state.matchesWifiConfiguration(wifiConfig) >= AccessPointState.MATCH_WEAK) { - return wifiConfig; - } - } - - return null; - } - - private List<WifiConfiguration> getConfiguredNetworks() { - final List<WifiConfiguration> wifiConfigs = mWifiManager.getConfiguredNetworks(); - return wifiConfigs; - } - - /** - * Must call while holding the lock for the list, which is usually the - * WifiLayer instance. - */ - private static AccessPointState findApLocked(List<AccessPointState> list, int networkId, - String bssid, String ssid, String security) { - AccessPointState ap; - for (int i = list.size() - 1; i >= 0; i--) { - ap = list.get(i); - if (ap.matches(networkId, bssid, ssid, security) >= AccessPointState.MATCH_WEAK) { - return ap; - } - } - - return null; - } - - /** - * Must call while holding the lock for the lists, which is usually this - * WifiLayer instance. - */ - private AccessPointState findApLocked(int networkId, String bssid, String ssid, - String security) { - AccessPointState ap = findApLocked(mApScanList, networkId, bssid, ssid, security); - if (ap == null) { - ap = findApLocked(mApOtherList, networkId, bssid, ssid, security); - } - return ap; - } - - /** - * Returns whether we have the exact instance of the access point state - * given. This is useful in cases where an AccessPointState has been - * parceled by the client and the client is attempting to use it to - * connect/forget/save. - * <p> - * Must call while holding the lock for the lists, which is usually this - * WifiLayer instance. - */ - private boolean hasApInstanceLocked(AccessPointState state) { - - for (int i = mApScanList.size() - 1; i >= 0; i--) { - if (mApScanList.get(i) == state) { - return true; - } - } - - for (int i = mApOtherList.size() - 1; i >= 0; i--) { - if (mApOtherList.get(i) == state) { - return true; - } - } - - return false; - } - - private void loadConfiguredAccessPoints() { - final List<WifiConfiguration> configs = getConfiguredNetworks(); - - for (int i = configs.size() - 1; i >= 0; i--) { - final WifiConfiguration config = configs.get(i); - - AccessPointState ap; - synchronized(this) { - ap = findApLocked(config.networkId, config.BSSID, config.SSID, - AccessPointState.getWifiConfigurationSecurity(config)); - - if (ap != null) { - // We already know about this one - continue; - } - - ap = new AccessPointState(mContext); - ap.updateFromWifiConfiguration(config); - if (LOGV) Log.v(TAG, "Created " + ap + " in loadConfiguredAccessPoints"); - mApOtherList.add(ap); - } - - // Make sure our next highest priority is greater than this - checkNextHighestPriority(ap.priority); - - if (mCallback != null) { - mCallback.onAccessPointSetChanged(ap, true); - } - } - } - - private AccessPointState getCurrentAp() { - final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - - String ssid = wifiInfo.getSSID(); - if (ssid != null) { - /* - * We pass null for security since we have a network ID (i.e., it's - * not a wildcard), and rely on it matching. - */ - synchronized (this) { - return findApLocked(wifiInfo.getNetworkId(), wifiInfo.getBSSID(), ssid, null); - } - } else { - return null; - } - } - - private void setPrimaryAp(AccessPointState ap) { - synchronized (this) { - // Unset other - if (mCurrentPrimaryAp != null) { - mCurrentPrimaryAp.setPrimary(false); - } - - mCurrentPrimaryAp = ap; - } - - if (ap != null) { - ap.setPrimary(true); - } - } - - private void attemptReenableAllAps() { - if (mReenableApsOnNetworkStateChange) { - mReenableApsOnNetworkStateChange = false; - enableAllAps(); - } - } - - private void enableAllAps() { - synchronized(this) { - if (LOGV) { - Log.v(TAG, " Enabling all APs"); - } - - enableApsLocked(mApOtherList); - enableApsLocked(mApScanList); - } - } - - private void enableApsLocked(List<AccessPointState> apList) { - for (int i = apList.size() - 1; i >= 0; i--) { - AccessPointState state = apList.get(i); - int networkId = state.networkId; - if (networkId != AccessPointState.NETWORK_ID_NOT_SET && - networkId != AccessPointState.NETWORK_ID_ANY) { - managerEnableNetwork(state, false); - } - } - } - - private void removeApFromUi(AccessPointState ap) { - if (mCallback != null) { - mCallback.onAccessPointSetChanged(ap, false); - } - } - - /** - * Sets the access point state to the highest priority. - * <p> - * If you have a list of configured networks from WifiManager, you probably - * shouldn't call this until you're done traversing the list. - * - * @param state The state to set as the highest priority. - * @param reusableConfiguration An optional WifiConfiguration that will be - * given to the WifiManager as updated data for the network ID. - * This will be filled with the new priority. - * @return Whether the operation was successful. - */ - private boolean setHighestPriorityStateAndSave(AccessPointState state, - WifiConfiguration reusableConfiguration) { - - if (!isConsideredForHighestPriority(state)) { - Log.e(TAG, - "Could not set highest priority on state because state is not being considered."); - return false; - } - - if (reusableConfiguration == null) { - reusableConfiguration = new WifiConfiguration(); - } - - int oldPriority = reusableConfiguration.priority; - reusableConfiguration.priority = getNextHighestPriority(); - reusableConfiguration.networkId = state.networkId; - - if (mWifiManager.updateNetwork(reusableConfiguration) == -1) { - // Rollback priority - reusableConfiguration.priority = oldPriority; - Log.e(TAG, - "Could not set highest priority on state because updating the supplicant network failed."); - return false; - } - - if (!managerSaveConfiguration()) { - reusableConfiguration.priority = oldPriority; - Log.e(TAG, - "Could not set highest priority on state because saving config failed."); - return false; - } - - state.priority = reusableConfiguration.priority; - - if (LOGV) { - Log.v(TAG, " Set highest priority to " - + state.priority + " from " + oldPriority); - } - - return true; - } - - /** - * Makes sure the next highest priority is larger than the given priority. - */ - private void checkNextHighestPriority(int priority) { - if (priority > HIGHEST_PRIORITY_MAX_VALUE || priority < 0) { - // This is a priority that we aren't managing - return; - } - - if (mNextHighestPriority <= priority) { - mNextHighestPriority = priority + 1; - } - } - - /** - * Checks if there are too many open networks, and removes the excess ones. - */ - private void checkForExcessOpenNetworks() { - synchronized(this) { - ArrayList<AccessPointState> allAps = getApsSortedByPriorityLocked(); - - // Walk from highest to lowest priority - int openConfiguredCount = 0; - for (int i = allAps.size() - 1; i >= 0; i--) { - AccessPointState state = allAps.get(i); - if (state.configured && !state.hasSecurity()) { - openConfiguredCount++; - if (openConfiguredCount > WIFI_NUM_OPEN_NETWORKS_KEPT) { - // Remove this network - forgetNetwork(state); - } - } - } - } - } - - private boolean isConsideredForHighestPriority(AccessPointState state) { - return state.configured && state.networkId != AccessPointState.NETWORK_ID_ANY && - state.networkId != AccessPointState.NETWORK_ID_NOT_SET; - } - - /** - * Gets the next highest priority. If this value is larger than the max, - * shift all the priorities so the lowest starts at 0. - * <p> - * Only - * {@link #setHighestPriorityStateAndSave(AccessPointState, WifiConfiguration)} - * should call this. - * - * @return The next highest priority to use. - */ - private int getNextHighestPriority() { - if (mNextHighestPriority > HIGHEST_PRIORITY_MAX_VALUE) { - shiftPriorities(); - } - - return mNextHighestPriority++; - } - - /** - * Shift all the priorities so the lowest starts at 0. - * - * @return Whether the operation was successful. - */ - private boolean shiftPriorities() { - synchronized(this) { - - ArrayList<AccessPointState> allAps = getApsSortedByPriorityLocked(); - - // Re-usable WifiConfiguration for setting priority - WifiConfiguration updatePriorityConfig = new WifiConfiguration(); - - // Set new priorities - mNextHighestPriority = 0; - int size = allAps.size(); - for (int i = 0; i < size; i++) { - AccessPointState state = allAps.get(i); - - if (!isConsideredForHighestPriority(state)) { - continue; - } - - if (!setHighestPriorityStateAndSave(state, updatePriorityConfig)) { - Log.e(TAG, - "Could not shift priorities because setting the new priority failed."); - return false; - } - } - - return true; - } - } - - private ArrayList<AccessPointState> getApsSortedByPriorityLocked() { - // Get all of the access points we have - ArrayList<AccessPointState> allAps = new ArrayList<AccessPointState>(mApScanList.size() - + mApOtherList.size()); - allAps.addAll(mApScanList); - allAps.addAll(mApOtherList); - - // Sort them based on priority - Collections.sort(allAps, new Comparator<AccessPointState>() { - public int compare(AccessPointState object1, AccessPointState object2) { - return object1.priority - object2.priority; - } - }); - - return allAps; - } - - //============================ - // Status related - //============================ - - private void refreshStatus() { - refreshStatus(null, null); - } - - private void refreshStatus(AccessPointState ap, NetworkInfo.DetailedState detailedState) { - final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - if (detailedState == null) { - detailedState = WifiInfo.getDetailedStateOf(wifiInfo.getSupplicantState()); - } - - if (ap == null && WifiStatus.isLiveConnection(detailedState)) { - /* - * We pass null for security since we have a network ID (i.e., it's - * not a wildcard), and rely on it matching. - */ - synchronized (this) { - ap = findApLocked(wifiInfo.getNetworkId(), wifiInfo.getBSSID(), wifiInfo - .getSSID(), null); - } - } - - if (ap != null) { - ap.blockRefresh(); - - // Let the AP get the latest info from the WifiInfo - ap.updateFromWifiInfo(wifiInfo, detailedState); - - // The detailed state from the Intent has more states than the WifiInfo's detailed - // state can have (for example, DHCP completion). Set the status using - // the Intent's detailed state. - ap.setStatus(detailedState); - ap.unblockRefresh(); - } - } - - //============================ - // Wifi callbacks - //============================ - - private void handleNetworkStateChanged(NetworkInfo info, String bssid) { - final AccessPointState ap = getCurrentAp(); - NetworkInfo.DetailedState detailedState = info.getDetailedState(); - - if (LOGV) { - Log.v(TAG, "State change received " + info.toString() + ", or " - + detailedState + " on " + bssid + " matched to " + ap); - } - - handleDisablingScanWhileObtainingAddress(detailedState); - - // This will update the AP with its new info - refreshStatus(ap, detailedState); - - boolean isDisconnected = info.getState().equals(State.DISCONNECTED); - if (ap != null && info.isConnectedOrConnecting()) { - setPrimaryAp(ap); - - if (LOGV) { - Log.v(TAG, " Updated " + ap + " to be primary"); - } - - } else if (isDisconnected) { - - /* - * When we drop off a network (for example, the router is powered - * down when we were connected), we received a DISCONNECT event - * without a BSSID. We should not have a primary AP anymore. - */ - setPrimaryAp(null); - - if (LOGV) { - Log.v(TAG, " Cleared primary"); - } - - } else if (detailedState.equals(DetailedState.FAILED)) { - - /* - * Doh, failed for whatever reason. Unset the primary AP, but set - * failed status on the AP that failed. - */ - setPrimaryAp(null); - ap.setStatus(DetailedState.FAILED); - - // Bring up error dialog - error(R.string.wifi_generic_connection_error); - - } else if (LOGV) { - Log.v(TAG, " Did not update any AP to primary, could have updated " - + ap + " but we aren't connected or connecting"); - } - - if ((ap != null) && (info.isConnected() - || (detailedState == DetailedState.OBTAINING_IPADDR))) { - /* - * Sometimes the scan results do not contain the AP even though it's - * clearly connected. This may be because we do passive background - * scanning that isn't as 'strong' as active scanning, so even - * though a network is nearby, it won't be seen by the passive - * scanning. If we are connected (or obtaining IP) then we know it - * is seen. - */ - ap.setSeen(true); - } - - attemptReenableAllAps(); - } - - private void handleDisablingScanWhileObtainingAddress(DetailedState detailedState) { - - if (detailedState == DetailedState.OBTAINING_IPADDR) { - mIsObtainingAddress = true; - - // We will not scan while obtaining an IP address - removeFutureScans(); - - } else { - mIsObtainingAddress = false; - - // Start continuous scan - queueContinuousScan(); - } - } - - private void handleScanResultsAvailable() { - synchronized(this) { - // In the end, we'll moved the ones no longer seen into the mApOtherList - List<AccessPointState> oldScanList = mApScanList; - List<AccessPointState> newScanList = - new ArrayList<AccessPointState>(oldScanList.size()); - - List<ScanResult> list = mWifiManager.getScanResults(); - if (list != null) { - for (int i = list.size() - 1; i >= 0; i--) { - final ScanResult scanResult = list.get(i); - - if (LOGV) { -// Log.v(TAG, " " + scanResult); - } - - if (scanResult == null) { - continue; - } - - /* - * Ignore adhoc, enterprise-secured, or hidden networks. - * Hidden networks show up with empty SSID. - */ - if (AccessPointState.isAdhoc(scanResult) - || TextUtils.isEmpty(scanResult.SSID)) { - continue; - } - - final String ssid = AccessPointState.convertToQuotedString(scanResult.SSID); - String security = AccessPointState.getScanResultSecurity(scanResult); - - // See if this AP is part of a group of APs (e.g., any large - // wifi network has many APs, we'll only show one) that we've - // seen in this scan - AccessPointState ap = findApLocked(newScanList, AccessPointState.NETWORK_ID_ANY, - AccessPointState.BSSID_ANY, ssid, security); - - // Yup, we've seen this network. - if (ap != null) { - // Use the better signal - if (WifiManager.compareSignalLevel(scanResult.level, ap.signal) > 0) { - ap.setSignal(scanResult.level); - } - - if (LOGV) { -// Log.v(TAG, " Already seen, continuing.."); - } - - continue; - } - - // Find the AP in either our old scan list, or our non-seen - // configured networks list - ap = findApLocked(AccessPointState.NETWORK_ID_ANY, AccessPointState.BSSID_ANY, - ssid, security); - - if (ap != null) { - // Remove the AP from both (no harm if one doesn't contain it) - oldScanList.remove(ap); - mApOtherList.remove(ap); - } else { - ap = new AccessPointState(mContext); -// if (LOGV) Log.v(TAG, "Created " + ap); - } - - // Give it the latest state - ap.updateFromScanResult(scanResult); - - if (mCallback != null) { - mCallback.onAccessPointSetChanged(ap, true); - } - - newScanList.add(ap); - } - } - - // oldScanList contains the ones no longer seen - List<AccessPointState> otherList = mApOtherList; - for (int i = oldScanList.size() - 1; i >= 0; i--) { - final AccessPointState ap = oldScanList.get(i); - - if (ap.configured) { - - // Keep it around, since it is configured - ap.setSeen(false); - otherList.add(ap); - - } else { - - // Remove it since it is not configured and not seen - removeApFromUi(ap); - } - } - - mApScanList = newScanList; - } - - onScanningEnded(); - } - - private void handleSupplicantConnectionChanged(boolean connected) { - if (mCallback != null) { - mCallback.onAccessPointsStateChanged(connected); - } - - if (connected) { - refreshAll(true); - } - } - - private void handleWifiStateChanged(int wifiState) { - - if (wifiState == WIFI_STATE_ENABLED) { - loadConfiguredAccessPoints(); - attemptScan(); - - } else if (wifiState == WIFI_STATE_DISABLED) { - removeFutureScans(); - if (LOGV) Log.v(TAG, "Clearing AP lists because wifi is disabled"); - clearApLists(); - } - - if (mCallback != null) { - mCallback.onAccessPointsStateChanged(wifiState == WIFI_STATE_ENABLED); - } - } - - private void handleSignalChanged(int rssi) { - - if (mCurrentPrimaryAp != null) { - mCurrentPrimaryAp.setSignal(rssi); - } - } - - private void handleSupplicantStateChanged(SupplicantState state, boolean hasError, int error) { - mCurrentSupplicantState = state; - - if (SupplicantState.FOUR_WAY_HANDSHAKE.equals(state)) { - mLastAuthenticatingAp = getCurrentAp(); - } - - if (hasError) { - handleSupplicantStateError(error); - } - } - - private void handleSupplicantStateError(int supplicantError) { - if (supplicantError == WifiManager.ERROR_AUTHENTICATING) { - if (mCallback != null) { - if (mLastAuthenticatingAp != null) { - mCallback.onRetryPassword(mLastAuthenticatingAp); - } - } - } - } - - private void handleNetworkIdsChanged() { - synchronized (this) { - final List<WifiConfiguration> configs = getConfiguredNetworks(); - - for (int i = configs.size() - 1; i >= 0; i--) { - final WifiConfiguration config = configs.get(i); - - AccessPointState ap; - // Since network IDs have changed, we can't use it to find our previous AP state - ap = findApLocked(AccessPointState.NETWORK_ID_ANY, config.BSSID, config.SSID, - AccessPointState.getWifiConfigurationSecurity(config)); - - if (ap == null) { - continue; - } - - ap.setNetworkId(config.networkId); - } - } - } - - private class MyHandler extends Handler { - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_ATTEMPT_SCAN: - attemptScan(); - break; - } - } - } - -} diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index cac77e3..d389cae 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * 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. @@ -19,463 +19,482 @@ package com.android.settings.wifi; import com.android.settings.ProgressCategory; import com.android.settings.R; -import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkInfo; +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.WifiConfiguration.Status; +import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; -import android.preference.CheckBoxPreference; -import android.provider.Settings; +import android.provider.Settings.Secure; import android.security.Credentials; import android.security.KeyStore; -import android.util.Log; +import android.text.TextUtils; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.ContextMenu.ContextMenuInfo; -import android.widget.AdapterView; -import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.Toast; -import java.util.Set; -import java.util.WeakHashMap; - -/** - * Settings screen for WiFi. This will be launched from the main system settings. - */ -public class WifiSettings extends PreferenceActivity implements WifiLayer.Callback, - DialogInterface.OnDismissListener { - - private static final String TAG = "WifiSettings"; - - //============================ - // Preference/activity member variables - //============================ - - private static final String INSTANCE_KEY_DIALOG_BUNDLE = - "com.android.settings.wifi.WifiSettings:dialogBundle"; - /* - * We don't use Activity's dialog management because AlertDialog isn't fully - * able to change many of its features after it's been created, and the - * dialog management only creates once. - */ - private Dialog mDialog; - - private static final String KEY_ONLY_ACCESS_POINTS = "only_access_points"; - private static final String KEY_ADD_OTHER_NETWORK = "add_other_network"; - - private static final int CONTEXT_MENU_ID_CONNECT = Menu.FIRST; - private static final int CONTEXT_MENU_ID_FORGET = Menu.FIRST + 1; - private static final int CONTEXT_MENU_ID_CHANGE_PASSWORD = Menu.FIRST + 2; +import java.util.ArrayList; +import java.util.List; +public class WifiSettings extends PreferenceActivity implements DialogInterface.OnClickListener { 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_WIFI_ENABLED = "wifi_enabled"; - private static final String KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED = - "open_network_notifications_enabled"; - private static final String KEY_ACCESS_POINTS = "access_points"; + private final IntentFilter mFilter; + private final BroadcastReceiver mReceiver; + private final Scanner mScanner; - private ProgressCategory mApCategory; - private CheckBoxPreference mWifiEnabled; + private WifiManager mWifiManager; private WifiEnabler mWifiEnabler; - private CheckBoxPreference mOpenNetworkNotificationsEnabled; - private Preference mAddOtherNetwork; + private CheckBoxPreference mNotifyOpenNetworks; + private ProgressCategory mAccessPoints; + private Preference mAddNetwork; - private WeakHashMap<AccessPointState, AccessPointPreference> mAps; + private DetailedState mLastState; + private WifiInfo mLastInfo; + private int mLastPriority; - private KeyStore mKeyStore = KeyStore.getInstance(); - private AccessPointState mResumeState = null; - private int mResumeMode; + private boolean mResetNetworks = false; + private int mKeyStoreNetworkId = -1; - //============================ - // Wifi member variables - //============================ - - private WifiLayer mWifiLayer; - - //============================ - // Activity lifecycle - //============================ + private AccessPoint mSelected; + private WifiDialog mDialog; public WifiSettings() { - mAps = new WeakHashMap<AccessPointState, AccessPointPreference>(); - mWifiLayer = new WifiLayer(this, this); + mFilter = new IntentFilter(); + mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); + mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleEvent(intent); + } + }; + + mScanner = new Scanner(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - onCreatePreferences(); - mWifiLayer.onCreate(); + mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); - onCreatedWifi(); - mWifiLayer.onCreatedCallback(); - } - - private int getPreferenceResource() { - if (getIntent().getBooleanExtra(KEY_ONLY_ACCESS_POINTS, false)) { - return R.xml.wifi_access_points; + if (getIntent().getBooleanExtra("only_access_points", false)) { + addPreferencesFromResource(R.xml.wifi_access_points); } else { - return R.xml.wifi_settings; - } - } - - /** - * Shouldn't have any dependency on the wifi layer. - */ - private void onCreatePreferences() { - addPreferencesFromResource(getPreferenceResource()); - - final PreferenceScreen preferenceScreen = getPreferenceScreen(); - - mApCategory = (ProgressCategory) preferenceScreen.findPreference(KEY_ACCESS_POINTS); - // We don't want the ordering to be the order preferences are added, - // instead we want*: - // 1) preferred, visible APs - // 2) visible APs - // 3) preferred, APs out of range - // * this ordering logic is in AccessPointPreference's compareTo - mApCategory.setOrderingAsAdded(false); - - if (!getIntent().getBooleanExtra(KEY_ONLY_ACCESS_POINTS, false)) { - mWifiEnabled = (CheckBoxPreference) preferenceScreen.findPreference(KEY_WIFI_ENABLED); - mWifiEnabler = new WifiEnabler(this, (WifiManager) getSystemService(WIFI_SERVICE), - mWifiEnabled); - - mOpenNetworkNotificationsEnabled = (CheckBoxPreference) preferenceScreen - .findPreference(KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED); - mOpenNetworkNotificationsEnabled.setChecked(Settings.Secure.getInt(getContentResolver(), - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1); + addPreferencesFromResource(R.xml.wifi_settings); + mWifiEnabler = new WifiEnabler(this, + (CheckBoxPreference) findPreference("enable_wifi")); + mNotifyOpenNetworks = + (CheckBoxPreference) findPreference("notify_open_networks"); + mNotifyOpenNetworks.setChecked(Secure.getInt(getContentResolver(), + Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1); } - mAddOtherNetwork = preferenceScreen.findPreference(KEY_ADD_OTHER_NETWORK); + mAccessPoints = (ProgressCategory) findPreference("access_points"); + mAccessPoints.setOrderingAsAdded(false); + mAddNetwork = findPreference("add_network"); registerForContextMenu(getListView()); } - private void onCreatedWifi() { - } - @Override protected void onResume() { super.onResume(); - mWifiLayer.onResume(); if (mWifiEnabler != null) { mWifiEnabler.resume(); } - // do what we should have after keystore is unlocked. - if (mResumeState != null) { - if (mKeyStore.test() == KeyStore.NO_ERROR) { - showAccessPointDialog(mResumeState, mResumeMode); - } - mResumeMode = -1; - mResumeState = null; - } else { - if (mResumeMode == AccessPointDialog.MODE_CONFIGURE) { - if (mKeyStore.test() == KeyStore.NO_ERROR) { - ((AccessPointDialog) mDialog).enableEnterpriseFields(); - } - } + registerReceiver(mReceiver, mFilter); + if (mKeyStoreNetworkId != -1 && KeyStore.getInstance().test() == KeyStore.NO_ERROR) { + connect(mKeyStoreNetworkId); } + mKeyStoreNetworkId = -1; } @Override protected void onPause() { super.onPause(); - mWifiLayer.onPause(); if (mWifiEnabler != null) { mWifiEnabler.pause(); } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - + unregisterReceiver(mReceiver); + mScanner.pause(); if (mDialog != null) { mDialog.dismiss(); + mDialog = null; + } + if (mResetNetworks) { + enableNetworks(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - menu.add(0, MENU_ID_SCAN, 0, R.string.scan_wifi) - .setIcon(R.drawable.ic_menu_scan_network); - - menu.add(0, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) - .setIcon(android.R.drawable.ic_menu_manage); - - return true; + menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan) + .setIcon(R.drawable.ic_menu_scan_network); + menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) + .setIcon(android.R.drawable.ic_menu_manage); + return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - switch (item.getItemId()) { - case MENU_ID_SCAN: - mWifiLayer.attemptScan(); + if (mWifiManager.isWifiEnabled()) { + mScanner.resume(); + } return true; - case MENU_ID_ADVANCED: - Intent intent = new Intent(this, AdvancedSettings.class); - startActivity(intent); + startActivity(new Intent(this, AdvancedSettings.class)); return true; - - default: - return false; } + return super.onOptionsItemSelected(item); } @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - if (mDialog != null) { - Bundle dialogBundle = mDialog.onSaveInstanceState(); - outState.putBundle(INSTANCE_KEY_DIALOG_BUNDLE, dialogBundle); - } - } - - @Override - protected void onRestoreInstanceState(Bundle state) { - super.onRestoreInstanceState(state); - - Bundle dialogBundle = state.getBundle(INSTANCE_KEY_DIALOG_BUNDLE); - if (dialogBundle != null) { - mDialog = new AccessPointDialog(this, mWifiLayer); - mDialog.onRestoreInstanceState(dialogBundle); - showDialog(mDialog); - } - } - - /** - * {@inheritDoc} - */ - public void onDismiss(DialogInterface dialog) { - if (dialog == mDialog) { - mDialog = null; - mResumeMode = -1; - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - - AccessPointState state = getStateFromMenuInfo(menuInfo); - if (state == null) { - return; - } - - menu.setHeaderTitle(state.getHumanReadableSsid()); - - if (state.isConnectable()) { - menu.add(0, CONTEXT_MENU_ID_CONNECT, 0, R.string.wifi_context_menu_connect); - } - - if (state.isForgetable()) { - menu.add(0, CONTEXT_MENU_ID_FORGET, 1, R.string.wifi_context_menu_forget); - - if (state.hasPassword()) { - menu.add(0, CONTEXT_MENU_ID_CHANGE_PASSWORD, 2, - R.string.wifi_context_menu_change_password); + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { + if (info instanceof AdapterContextMenuInfo) { + Preference preference = (Preference) getListView().getItemAtPosition( + ((AdapterContextMenuInfo) info).position); + + if (preference instanceof AccessPoint) { + mSelected = (AccessPoint) preference; + menu.setHeaderTitle(mSelected.ssid); + if (mSelected.getLevel() != -1 && mSelected.getState() == null) { + menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect); + } + if (mSelected.networkId != -1) { + menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget); + if (mSelected.security != AccessPoint.SECURITY_NONE) { + menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify); + } + } } } } @Override public boolean onContextItemSelected(MenuItem item) { - - AccessPointState state = getStateFromMenuInfo(item.getMenuInfo()); - if (state == null) { - return false; + if (mSelected == null) { + return super.onContextItemSelected(item); } - switch (item.getItemId()) { - - case CONTEXT_MENU_ID_CONNECT: - connectToNetwork(state); + case MENU_ID_CONNECT: + if (mSelected.networkId != -1) { + if (!requireKeyStore(mSelected.getConfig())) { + connect(mSelected.networkId); + } + } else if (mSelected.security == AccessPoint.SECURITY_NONE) { + // Shortcut for open networks. + WifiConfiguration config = new WifiConfiguration(); + config.SSID = AccessPoint.convertToQuotedString(mSelected.ssid); + config.allowedKeyManagement.set(KeyMgmt.NONE); + int networkId = mWifiManager.addNetwork(config); + mWifiManager.enableNetwork(networkId, false); + connect(networkId); + } else { + showDialog(mSelected, false); + } return true; - - case CONTEXT_MENU_ID_FORGET: - mWifiLayer.forgetNetwork(state); + case MENU_ID_FORGET: + forget(mSelected.networkId); return true; - - case CONTEXT_MENU_ID_CHANGE_PASSWORD: - showAccessPointDialog(state, AccessPointDialog.MODE_CONFIGURE); + case MENU_ID_MODIFY: + showDialog(mSelected, true); return true; - - default: - return false; } + return super.onContextItemSelected(item); } - /** - * Decides what needs to happen to connect to a particular access point. If - * it is secured and doesn't already have a password, it will bring up a - * password box. Otherwise it will just connect. - */ - private void connectToNetwork(AccessPointState state) { - if (state.hasSecurity() && !state.hasPassword()) { - showAccessPointDialog(state, AccessPointDialog.MODE_INFO); + @Override + public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { + if (preference instanceof AccessPoint) { + mSelected = (AccessPoint) preference; + showDialog(mSelected, false); + } else if (preference == mAddNetwork) { + mSelected = null; + showDialog(null, true); + } else if (preference == mNotifyOpenNetworks) { + Secure.putInt(getContentResolver(), + Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + mNotifyOpenNetworks.isChecked() ? 1 : 0); } else { - mWifiLayer.connectToNetwork(state); + return super.onPreferenceTreeClick(screen, preference); } + return true; } - private AccessPointState getStateFromMenuInfo(ContextMenuInfo menuInfo) { - if ((menuInfo == null) || !(menuInfo instanceof AdapterContextMenuInfo)) { - return null; - } + public void onClick(DialogInterface dialogInterface, int button) { + if (button == WifiDialog.BUTTON_FORGET && mSelected != null) { + forget(mSelected.networkId); + } else if (button == WifiDialog.BUTTON_SUBMIT && mDialog != null) { + WifiConfiguration config = mDialog.getConfig(); - AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo; - Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem( - adapterMenuInfo.position); - if (pref == null || !(pref instanceof AccessPointPreference)) { - return null; + if (config == null) { + if (mSelected != null && !requireKeyStore(mSelected.getConfig())) { + connect(mSelected.networkId); + } + } else if (config.networkId != -1) { + if (mSelected != null) { + mWifiManager.updateNetwork(config); + saveNetworks(); + } + } else { + int networkId = mWifiManager.addNetwork(config); + if (networkId != -1) { + mWifiManager.enableNetwork(networkId, false); + config.networkId = networkId; + if (mDialog.edit || requireKeyStore(config)) { + saveNetworks(); + } else { + connect(networkId); + } + } + } } - - return ((AccessPointPreference) pref).getAccessPointState(); } - //============================ - // Preference callbacks - //============================ - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - super.onPreferenceTreeClick(preferenceScreen, preference); - - if (preference == mAddOtherNetwork) { - showAddOtherNetworkDialog(); - } else if (preference == mOpenNetworkNotificationsEnabled) { - Settings.Secure.putInt(getContentResolver(), - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, - mOpenNetworkNotificationsEnabled.isChecked() ? 1 : 0); - } else if (preference instanceof AccessPointPreference) { - AccessPointState state = ((AccessPointPreference) preference).getAccessPointState(); - showAccessPointDialog(state, AccessPointDialog.MODE_INFO); + private void showDialog(AccessPoint accessPoint, boolean edit) { + if (mDialog != null) { + mDialog.dismiss(); } - - return false; + mDialog = new WifiDialog(this, this, accessPoint, edit); + mDialog.show(); } - //============================ - // Wifi-related - //============================ - - public WifiLayer getWifiLayer() { - return mWifiLayer; + private boolean requireKeyStore(WifiConfiguration config) { + if (WifiDialog.requireKeyStore(config) && + KeyStore.getInstance().test() != KeyStore.NO_ERROR) { + mKeyStoreNetworkId = config.networkId; + Credentials.getInstance().unlock(this); + return true; + } + return false; } - private void showAddOtherNetworkDialog() { - AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer); - dialog.setState(new AccessPointState(this)); - dialog.setMode(AccessPointDialog.MODE_CONFIGURE); - dialog.setTitle(R.string.wifi_add_other_network); - dialog.setAutoSecurityAllowed(false); - mResumeMode = AccessPointDialog.MODE_CONFIGURE; - showDialog(dialog); + private void forget(int networkId) { + mWifiManager.removeNetwork(networkId); + saveNetworks(); } - public void showAccessPointDialog(AccessPointState state, int mode) { - if (state.isEnterprise() && mKeyStore.test() != KeyStore.NO_ERROR) { - Credentials.getInstance().unlock(this); - mResumeState = state; - mResumeMode = mode; + private void connect(int networkId) { + if (networkId == -1) { return; } - AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer); - dialog.setMode(mode); - dialog.setState(state); - showDialog(dialog); - } - private void showDialog(Dialog dialog) { - // Have only one dialog open at a time - if (mDialog != null) { - mDialog.dismiss(); + // Reset the priority of each network if it goes too high. + if (mLastPriority > 1000000) { + for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) { + AccessPoint accessPoint = (AccessPoint) mAccessPoints.getPreference(i); + if (accessPoint.networkId != -1) { + WifiConfiguration config = new WifiConfiguration(); + config.networkId = accessPoint.networkId; + config.priority = 0; + mWifiManager.updateNetwork(config); + } + } + mLastPriority = 0; } - mDialog = dialog; - if (dialog != null) { - dialog.setOnDismissListener(this); - dialog.show(); - } + // Set to the highest priority and save the configuration. + WifiConfiguration config = new WifiConfiguration(); + config.networkId = networkId; + config.priority = ++mLastPriority; + mWifiManager.updateNetwork(config); + saveNetworks(); + + // Connect to network by disabling others. + mWifiManager.enableNetwork(networkId, true); + mWifiManager.reconnect(); + mResetNetworks = true; } - //============================ - // Wifi callbacks - //============================ - - public void onError(int messageResId) { - Toast.makeText(this, messageResId, Toast.LENGTH_LONG).show(); + private void enableNetworks() { + for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) { + WifiConfiguration config = ((AccessPoint) mAccessPoints.getPreference(i)).getConfig(); + if (config != null && config.status != Status.ENABLED) { + mWifiManager.enableNetwork(config.networkId, false); + } + } + mResetNetworks = false; } - public void onScanningStatusChanged(boolean started) { - mApCategory.setProgress(started); + private void saveNetworks() { + // Always save the configuration with all networks enabled. + enableNetworks(); + mWifiManager.saveConfiguration(); + updateAccessPoints(); } - public void onAccessPointSetChanged(AccessPointState ap, boolean added) { + private void updateAccessPoints() { + List<AccessPoint> accessPoints = new ArrayList<AccessPoint>(); - AccessPointPreference pref = mAps.get(ap); + List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + if (configs != null) { + mLastPriority = 0; + for (WifiConfiguration config : configs) { + if (config.priority > mLastPriority) { + mLastPriority = config.priority; + } - if (WifiLayer.LOGV) { - Log.v(TAG, "onAccessPointSetChanged with " + ap + " and " - + (added ? "added" : "removed") + ", found pref " + pref); + // Shift the status to make enableNetworks() more efficient. + if (config.status == Status.CURRENT) { + config.status = Status.ENABLED; + } else if (mResetNetworks && config.status == Status.DISABLED) { + config.status = Status.CURRENT; + } + + AccessPoint accessPoint = new AccessPoint(this, config); + accessPoint.update(mLastInfo, mLastState); + accessPoints.add(accessPoint); + } } - if (added) { + List<ScanResult> results = mWifiManager.getScanResults(); + if (results != null) { + for (ScanResult result : results) { + // Ignore hidden and ad-hoc networks. + if (result.SSID == null || result.SSID.length() == 0 || + result.capabilities.contains("[IBSS]")) { + continue; + } - if (pref == null) { - pref = new AccessPointPreference(this, ap); - mAps.put(ap, pref); - } else { - pref.setEnabled(true); + boolean found = false; + for (AccessPoint accessPoint : accessPoints) { + if (accessPoint.update(result)) { + found = true; + } + } + if (!found) { + accessPoints.add(new AccessPoint(this, result)); + } } + } - mApCategory.addPreference(pref); + mAccessPoints.removeAll(); + for (AccessPoint accessPoint : accessPoints) { + mAccessPoints.addPreference(accessPoint); + } + } + private void handleEvent(Intent intent) { + String action = intent.getAction(); + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { + updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN)); + } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { + updateAccessPoints(); + } else if (WifiManager.NETWORK_IDS_CHANGED_ACTION.equals(action)) { + if (mSelected != null && mSelected.networkId != -1) { + mSelected = null; + } + updateAccessPoints(); + } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) { + updateConnectionState(WifiInfo.getDetailedStateOf((SupplicantState) + intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE))); + } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { + updateConnectionState(((NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO)).getDetailedState()); + } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { + updateConnectionState(null); + } + } + + private void updateConnectionState(DetailedState state) { + /* sticky broadcasts can call this when wifi is disabled */ + if (!mWifiManager.isWifiEnabled()) { + mScanner.pause(); + return; + } + + if (state == DetailedState.OBTAINING_IPADDR) { + mScanner.pause(); } else { + mScanner.resume(); + } - mAps.remove(ap); + mLastInfo = mWifiManager.getConnectionInfo(); + if (state != null) { + mLastState = state; + } - if (pref != null) { - mApCategory.removePreference(pref); - } + for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) { + ((AccessPoint) mAccessPoints.getPreference(i)).update(mLastInfo, mLastState); + } + if (mResetNetworks && (state == DetailedState.CONNECTED || + state == DetailedState.DISCONNECTED || state == DetailedState.FAILED)) { + updateAccessPoints(); + enableNetworks(); } } - public void onAccessPointsStateChanged(boolean enabled) { - if (enabled) { - mApCategory.setEnabled(true); + private void updateWifiState(int state) { + if (state == WifiManager.WIFI_STATE_ENABLED) { + mScanner.resume(); + updateAccessPoints(); } else { - mApCategory.removeAll(); - mAps.clear(); + mScanner.pause(); + mAccessPoints.removeAll(); } - - mAddOtherNetwork.setEnabled(enabled); } - public void onRetryPassword(AccessPointState ap) { + private class Scanner extends Handler { + private int mRetry = 0; - if ((mDialog != null) && mDialog.isShowing()) { - // If we're already showing a dialog, ignore this request - return; + void resume() { + if (!hasMessages(0)) { + sendEmptyMessage(0); + } } - showAccessPointDialog(ap, AccessPointDialog.MODE_RETRY_PASSWORD); - } + void pause() { + mRetry = 0; + mAccessPoints.setProgress(false); + removeMessages(0); + } + @Override + public void handleMessage(Message message) { + if (mWifiManager.startScanActive()) { + mRetry = 0; + } else if (++mRetry >= 3) { + mRetry = 0; + Toast.makeText(WifiSettings.this, R.string.wifi_fail_to_scan, + Toast.LENGTH_LONG).show(); + return; + } + mAccessPoints.setProgress(mRetry != 0); + sendEmptyMessageDelayed(0, 6000); + } + } } diff --git a/src/com/android/settings/wifi/WifiStatus.java b/src/com/android/settings/wifi/WifiStatus.java deleted file mode 100644 index e10ab8d..0000000 --- a/src/com/android/settings/wifi/WifiStatus.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.settings.R; - -import android.content.Context; -import android.net.NetworkInfo; -import android.text.TextUtils; - -public class WifiStatus { - public static String getStatus(Context context, String ssid, - NetworkInfo.DetailedState detailedState) { - - if (!TextUtils.isEmpty(ssid) && isLiveConnection(detailedState)) { - return getPrintableFragment(context, detailedState, ssid); - } else { - return getPrintable(context, detailedState); - } - } - - public static boolean isLiveConnection(NetworkInfo.DetailedState detailedState) { - return detailedState != NetworkInfo.DetailedState.DISCONNECTED - && detailedState != NetworkInfo.DetailedState.FAILED - && detailedState != NetworkInfo.DetailedState.IDLE - && detailedState != NetworkInfo.DetailedState.SCANNING; - } - - public static String getPrintable(Context context, - NetworkInfo.DetailedState detailedState) { - - switch (detailedState) { - case AUTHENTICATING: - return context.getString(R.string.status_authenticating); - case CONNECTED: - return context.getString(R.string.status_connected); - case CONNECTING: - return context.getString(R.string.status_connecting); - case DISCONNECTED: - return context.getString(R.string.status_disconnected); - case DISCONNECTING: - return context.getString(R.string.status_disconnecting); - case FAILED: - return context.getString(R.string.status_failed); - case OBTAINING_IPADDR: - return context.getString(R.string.status_obtaining_ip); - case SCANNING: - return context.getString(R.string.status_scanning); - default: - return null; - } - } - - public static String getPrintableFragment(Context context, - NetworkInfo.DetailedState detailedState, String apName) { - - String fragment = null; - switch (detailedState) { - case AUTHENTICATING: - fragment = context.getString(R.string.fragment_status_authenticating); - break; - case CONNECTED: - fragment = context.getString(R.string.fragment_status_connected); - break; - case CONNECTING: - fragment = context.getString(R.string.fragment_status_connecting); - break; - case DISCONNECTED: - fragment = context.getString(R.string.fragment_status_disconnected); - break; - case DISCONNECTING: - fragment = context.getString(R.string.fragment_status_disconnecting); - break; - case FAILED: - fragment = context.getString(R.string.fragment_status_failed); - break; - case OBTAINING_IPADDR: - fragment = context.getString(R.string.fragment_status_obtaining_ip); - break; - case SCANNING: - fragment = context.getString(R.string.fragment_status_scanning); - break; - } - - return String.format(fragment, apName); - } - -} diff --git a/src/com/android/settings/wifi/WifiStatusTest.java b/src/com/android/settings/wifi/WifiStatusTest.java new file mode 100644 index 0000000..1b23834 --- /dev/null +++ b/src/com/android/settings/wifi/WifiStatusTest.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2009 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 com.android.settings.R; +import android.net.wifi.ScanResult; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import java.util.List; +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkInfo; +import android.net.wifi.SupplicantState; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.TextView; +import java.io.IOException; +import java.net.UnknownHostException; + + +/** + * Show the current status details of Wifi related fields + */ +public class WifiStatusTest extends Activity { + + private static final String TAG = "WifiStatusTest"; + + private Button updateButton; + private TextView mWifiState; + private TextView mNetworkState; + private TextView mSupplicantState; + private TextView mRSSI; + private TextView mBSSID; + private TextView mSSID; + private TextView mHiddenSSID; + private TextView mIPAddr; + private TextView mMACAddr; + private TextView mNetworkId; + private TextView mLinkSpeed; + private TextView mScanList; + + + private TextView mPingIpAddr; + private TextView mPingHostname; + private TextView mHttpClientTest; + private Button pingTestButton; + + private String mPingIpAddrResult; + private String mPingHostnameResult; + private String mHttpClientTestResult; + + + private WifiManager mWifiManager; + private IntentFilter mWifiStateFilter; + + + //============================ + // Activity lifecycle + //============================ + + private final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN)); + } else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + handleNetworkStateChanged( + (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)); + } else if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + handleScanResultsAvailable(); + } else if (intent.getAction().equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) { + /* TODO: handle supplicant connection change later */ + } else if (intent.getAction().equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { + handleSupplicantStateChanged( + (SupplicantState) intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE), + intent.hasExtra(WifiManager.EXTRA_SUPPLICANT_ERROR), + intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0)); + } else if (intent.getAction().equals(WifiManager.RSSI_CHANGED_ACTION)) { + handleSignalChanged(intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, 0)); + } else if (intent.getAction().equals(WifiManager.NETWORK_IDS_CHANGED_ACTION)) { + /* TODO: handle network id change info later */ + } else { + Log.e(TAG, "Received an unknown Wifi Intent"); + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE); + + mWifiStateFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); + mWifiStateFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mWifiStateFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mWifiStateFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + mWifiStateFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + mWifiStateFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + + registerReceiver(mWifiStateReceiver, mWifiStateFilter); + + setContentView(R.layout.wifi_status_test); + + updateButton = (Button) findViewById(R.id.update); + updateButton.setOnClickListener(updateButtonHandler); + + mWifiState = (TextView) findViewById(R.id.wifi_state); + mNetworkState = (TextView) findViewById(R.id.network_state); + mSupplicantState = (TextView) findViewById(R.id.supplicant_state); + mRSSI = (TextView) findViewById(R.id.rssi); + mBSSID = (TextView) findViewById(R.id.bssid); + mSSID = (TextView) findViewById(R.id.ssid); + mHiddenSSID = (TextView) findViewById(R.id.hidden_ssid); + mIPAddr = (TextView) findViewById(R.id.ipaddr); + mMACAddr = (TextView) findViewById(R.id.macaddr); + mNetworkId = (TextView) findViewById(R.id.networkid); + mLinkSpeed = (TextView) findViewById(R.id.link_speed); + mScanList = (TextView) findViewById(R.id.scan_list); + + + mPingIpAddr = (TextView) findViewById(R.id.pingIpAddr); + mPingHostname = (TextView) findViewById(R.id.pingHostname); + mHttpClientTest = (TextView) findViewById(R.id.httpClientTest); + + pingTestButton = (Button) findViewById(R.id.ping_test); + pingTestButton.setOnClickListener(mPingButtonHandler); + } + + @Override + protected void onResume() { + super.onResume(); + registerReceiver(mWifiStateReceiver, mWifiStateFilter); + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(mWifiStateReceiver); + } + + OnClickListener mPingButtonHandler = new OnClickListener() { + public void onClick(View v) { + updatePingState(); + } + }; + + OnClickListener updateButtonHandler = new OnClickListener() { + public void onClick(View v) { + final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + + setWifiStateText(mWifiManager.getWifiState()); + mBSSID.setText(wifiInfo.getBSSID()); + mHiddenSSID.setText(String.valueOf(wifiInfo.getHiddenSSID())); + int ipAddr = wifiInfo.getIpAddress(); + StringBuffer ipBuf = new StringBuffer(); + ipBuf.append(ipAddr & 0xff).append('.'). + append((ipAddr >>>= 8) & 0xff).append('.'). + append((ipAddr >>>= 8) & 0xff).append('.'). + append((ipAddr >>>= 8) & 0xff); + + mIPAddr.setText(ipBuf); + mLinkSpeed.setText(String.valueOf(wifiInfo.getLinkSpeed())+" Mbps"); + mMACAddr.setText(wifiInfo.getMacAddress()); + mNetworkId.setText(String.valueOf(wifiInfo.getNetworkId())); + mRSSI.setText(String.valueOf(wifiInfo.getRssi())); + mSSID.setText(wifiInfo.getSSID()); + + SupplicantState supplicantState = wifiInfo.getSupplicantState(); + setSupplicantStateText(supplicantState); + } + }; + + private void setSupplicantStateText(SupplicantState supplicantState) { + if(SupplicantState.FOUR_WAY_HANDSHAKE.equals(supplicantState)) { + mSupplicantState.setText("FOUR WAY HANDSHAKE"); + } else if(SupplicantState.ASSOCIATED.equals(supplicantState)) { + mSupplicantState.setText("ASSOCIATED"); + } else if(SupplicantState.ASSOCIATING.equals(supplicantState)) { + mSupplicantState.setText("ASSOCIATING"); + } else if(SupplicantState.COMPLETED.equals(supplicantState)) { + mSupplicantState.setText("COMPLETED"); + } else if(SupplicantState.DISCONNECTED.equals(supplicantState)) { + mSupplicantState.setText("DISCONNECTED"); + } else if(SupplicantState.DORMANT.equals(supplicantState)) { + mSupplicantState.setText("DORMANT"); + } else if(SupplicantState.GROUP_HANDSHAKE.equals(supplicantState)) { + mSupplicantState.setText("GROUP HANDSHAKE"); + } else if(SupplicantState.INACTIVE.equals(supplicantState)) { + mSupplicantState.setText("INACTIVE"); + } else if(SupplicantState.INVALID.equals(supplicantState)) { + mSupplicantState.setText("INVALID"); + } else if(SupplicantState.SCANNING.equals(supplicantState)) { + mSupplicantState.setText("SCANNING"); + } else if(SupplicantState.UNINITIALIZED.equals(supplicantState)) { + mSupplicantState.setText("UNINITIALIZED"); + } else { + mSupplicantState.setText("BAD"); + Log.e(TAG, "supplicant state is bad"); + } + } + + private void setWifiStateText(int wifiState) { + String wifiStateString; + switch(wifiState) { + case WifiManager.WIFI_STATE_DISABLING: + wifiStateString = getString(R.string.wifi_state_disabling); + break; + case WifiManager.WIFI_STATE_DISABLED: + wifiStateString = getString(R.string.wifi_state_disabled); + break; + case WifiManager.WIFI_STATE_ENABLING: + wifiStateString = getString(R.string.wifi_state_enabling); + break; + case WifiManager.WIFI_STATE_ENABLED: + wifiStateString = getString(R.string.wifi_state_enabled); + break; + case WifiManager.WIFI_STATE_UNKNOWN: + wifiStateString = getString(R.string.wifi_state_unknown); + break; + default: + wifiStateString = "BAD"; + Log.e(TAG, "wifi state is bad"); + break; + } + + mWifiState.setText(wifiStateString); + } + + private void handleSignalChanged(int rssi) { + mRSSI.setText(String.valueOf(rssi)); + } + + private void handleWifiStateChanged(int wifiState) { + setWifiStateText(wifiState); + } + + private void handleScanResultsAvailable() { + List<ScanResult> list = mWifiManager.getScanResults(); + + StringBuffer scanList = new StringBuffer(); + if (list != null) { + for (int i = list.size() - 1; i >= 0; i--) { + final ScanResult scanResult = list.get(i); + + if (scanResult == null) { + continue; + } + + if (TextUtils.isEmpty(scanResult.SSID)) { + continue; + } + + scanList.append(scanResult.SSID+" "); + } + } + mScanList.setText(scanList); + } + + private void handleSupplicantStateChanged(SupplicantState state, boolean hasError, int error) { + if (hasError) { + mSupplicantState.setText("ERROR AUTHENTICATING"); + } else { + setSupplicantStateText(state); + } + } + + private void handleNetworkStateChanged(NetworkInfo networkInfo) { + if (mWifiManager.isWifiEnabled()) { + String summary = Summary.get(this, mWifiManager.getConnectionInfo().getSSID(), + networkInfo.getDetailedState()); + mNetworkState.setText(summary); + } + } + + private final void updatePingState() { + final Handler handler = new Handler(); + // Set all to unknown since the threads will take a few secs to update. + mPingIpAddrResult = getResources().getString(R.string.radioInfo_unknown); + mPingHostnameResult = getResources().getString(R.string.radioInfo_unknown); + mHttpClientTestResult = getResources().getString(R.string.radioInfo_unknown); + + mPingIpAddr.setText(mPingIpAddrResult); + mPingHostname.setText(mPingHostnameResult); + mHttpClientTest.setText(mHttpClientTestResult); + + final Runnable updatePingResults = new Runnable() { + public void run() { + mPingIpAddr.setText(mPingIpAddrResult); + mPingHostname.setText(mPingHostnameResult); + mHttpClientTest.setText(mHttpClientTestResult); + } + }; + Thread ipAddrThread = new Thread() { + @Override + public void run() { + pingIpAddr(); + handler.post(updatePingResults); + } + }; + ipAddrThread.start(); + + Thread hostnameThread = new Thread() { + @Override + public void run() { + pingHostname(); + handler.post(updatePingResults); + } + }; + hostnameThread.start(); + + Thread httpClientThread = new Thread() { + @Override + public void run() { + httpClientTest(); + handler.post(updatePingResults); + } + }; + httpClientThread.start(); + } + + /** + * The ping functions have been borrowed from Radio diagnostic app to + * enable quick access on the wifi status screen + */ + private final void pingIpAddr() { + try { + // TODO: Hardcoded for now, make it UI configurable + String ipAddress = "74.125.47.104"; + Process p = Runtime.getRuntime().exec("ping -c 1 -w 100 " + ipAddress); + int status = p.waitFor(); + if (status == 0) { + mPingIpAddrResult = "Pass"; + } else { + mPingIpAddrResult = "Fail: IP addr not reachable"; + } + } catch (IOException e) { + mPingIpAddrResult = "Fail: IOException"; + } catch (InterruptedException e) { + mPingIpAddrResult = "Fail: InterruptedException"; + } + } + + private final void pingHostname() { + try { + // TODO: Hardcoded for now, make it UI configurable + Process p = Runtime.getRuntime().exec("ping -c 1 -w 100 www.google.com"); + int status = p.waitFor(); + if (status == 0) { + mPingHostnameResult = "Pass"; + } else { + mPingHostnameResult = "Fail: Host unreachable"; + } + } catch (UnknownHostException e) { + mPingHostnameResult = "Fail: Unknown Host"; + } catch (IOException e) { + mPingHostnameResult= "Fail: IOException"; + } catch (InterruptedException e) { + mPingHostnameResult = "Fail: InterruptedException"; + } + } + + private void httpClientTest() { + HttpClient client = new DefaultHttpClient(); + try { + // TODO: Hardcoded for now, make it UI configurable + HttpGet request = new HttpGet("http://www.google.com"); + HttpResponse response = client.execute(request); + if (response.getStatusLine().getStatusCode() == 200) { + mHttpClientTestResult = "Pass"; + } else { + mHttpClientTestResult = "Fail: Code: " + String.valueOf(response); + } + request.abort(); + } catch (IOException e) { + mHttpClientTestResult = "Fail: IOException"; + } + } + +} |