summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/settings/AccessibilityEnableScriptInjectionPreference.java175
-rw-r--r--src/com/android/settings/AccessibilitySettings.java1021
-rw-r--r--src/com/android/settings/AccessibilityTutorialActivity.java664
-rw-r--r--src/com/android/settings/AccountPreference.java15
-rw-r--r--src/com/android/settings/ActivityPicker.java2
-rw-r--r--src/com/android/settings/AirplaneModeEnabler.java11
-rw-r--r--src/com/android/settings/ApplicationSettings.java80
-rw-r--r--src/com/android/settings/BatteryInfo.java23
-rw-r--r--src/com/android/settings/BrightnessPreference.java4
-rw-r--r--src/com/android/settings/ChooseLockGeneric.java46
-rw-r--r--src/com/android/settings/ChooseLockPassword.java6
-rw-r--r--src/com/android/settings/ChooseLockPattern.java2
-rw-r--r--src/com/android/settings/ChooseLockSettingsHelper.java8
-rw-r--r--src/com/android/settings/ConfirmLockPassword.java33
-rw-r--r--src/com/android/settings/ConfirmLockPattern.java7
-rw-r--r--src/com/android/settings/CredentialStorage.java492
-rw-r--r--src/com/android/settings/CryptKeeper.java89
-rw-r--r--src/com/android/settings/CryptKeeperConfirm.java2
-rw-r--r--src/com/android/settings/CryptKeeperSettings.java24
-rw-r--r--src/com/android/settings/DataUsageSummary.java1713
-rw-r--r--src/com/android/settings/DateTimeSettings.java5
-rw-r--r--src/com/android/settings/DateTimeSettingsSetupWizard.java4
-rw-r--r--src/com/android/settings/DefaultRingtonePreference.java1
-rw-r--r--src/com/android/settings/DevelopmentSettings.java280
-rw-r--r--src/com/android/settings/DeviceInfoSettings.java21
-rw-r--r--src/com/android/settings/Display.java2
-rw-r--r--src/com/android/settings/DisplaySettings.java166
-rw-r--r--src/com/android/settings/DreamComponentPreference.java139
-rw-r--r--src/com/android/settings/DreamSettings.java128
-rw-r--r--src/com/android/settings/DreamTesterPreference.java75
-rw-r--r--src/com/android/settings/GoogleLocationSettingHelper.java1
-rw-r--r--src/com/android/settings/LocationSettings.java192
-rw-r--r--src/com/android/settings/MasterClear.java9
-rw-r--r--src/com/android/settings/PointerSpeedPreference.java4
-rw-r--r--src/com/android/settings/PrivacySettings.java19
-rw-r--r--src/com/android/settings/ProgressCategory.java30
-rw-r--r--src/com/android/settings/RadioInfo.java24
-rw-r--r--src/com/android/settings/RingerVolumePreference.java92
-rw-r--r--src/com/android/settings/SecuritySettings.java202
-rw-r--r--src/com/android/settings/SetFullBackupPassword.java106
-rw-r--r--src/com/android/settings/Settings.java365
-rw-r--r--src/com/android/settings/SettingsLicenseActivity.java3
-rw-r--r--src/com/android/settings/SoundSettings.java109
-rw-r--r--src/com/android/settings/SubSettings.java24
-rw-r--r--src/com/android/settings/TestingSettingsBroadcastReceiver.java1
-rw-r--r--src/com/android/settings/TetherSettings.java77
-rw-r--r--src/com/android/settings/TextToSpeechSettings.java642
-rw-r--r--src/com/android/settings/TrustedCredentialsSettings.java417
-rw-r--r--src/com/android/settings/UserDictionarySettings.java117
-rw-r--r--src/com/android/settings/Utils.java57
-rw-r--r--src/com/android/settings/WallpaperTypeSettings.java61
-rw-r--r--src/com/android/settings/WirelessSettings.java79
-rw-r--r--src/com/android/settings/accounts/AccountPreferenceBase.java14
-rw-r--r--src/com/android/settings/accounts/AccountSyncSettings.java38
-rw-r--r--src/com/android/settings/accounts/AddAccountSettings.java44
-rw-r--r--src/com/android/settings/accounts/ChooseAccountActivity.java10
-rw-r--r--src/com/android/settings/accounts/ManageAccountsSettings.java9
-rw-r--r--src/com/android/settings/applications/ApplicationsState.java65
-rw-r--r--src/com/android/settings/applications/InstalledAppDetails.java50
-rw-r--r--src/com/android/settings/applications/ManageApplications.java53
-rw-r--r--src/com/android/settings/applications/RunningProcessesView.java108
-rw-r--r--src/com/android/settings/applications/RunningState.java25
-rw-r--r--src/com/android/settings/bluetooth/A2dpProfile.java6
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDeviceFilter.java10
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDevicePreference.java118
-rwxr-xr-x[-rw-r--r--]src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java159
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEnabler.java84
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEventManager.java6
-rw-r--r--src/com/android/settings/bluetooth/BluetoothFindNearby.java71
-rw-r--r--src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java161
-rw-r--r--src/com/android/settings/bluetooth/BluetoothNamePreference.java143
-rwxr-xr-x[-rw-r--r--]src/com/android/settings/bluetooth/BluetoothPairingDialog.java106
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPairingRequest.java2
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPermissionActivity.java232
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPermissionRequest.java107
-rw-r--r--src/com/android/settings/bluetooth/BluetoothProfilePreference.java96
-rw-r--r--src/com/android/settings/bluetooth/BluetoothSettings.java355
-rw-r--r--src/com/android/settings/bluetooth/BluetoothVisibilityTimeoutFragment.java67
-rw-r--r--src/com/android/settings/bluetooth/CachedBluetoothDevice.java14
-rw-r--r--src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java19
-rw-r--r--src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java47
-rw-r--r--src/com/android/settings/bluetooth/DevicePickerFragment.java4
-rwxr-xr-x[-rw-r--r--]src/com/android/settings/bluetooth/DeviceProfilesSettings.java159
-rw-r--r--src/com/android/settings/bluetooth/DockEventReceiver.java2
-rw-r--r--src/com/android/settings/bluetooth/DockService.java2
-rw-r--r--src/com/android/settings/bluetooth/HeadsetProfile.java6
-rw-r--r--src/com/android/settings/bluetooth/HidProfile.java7
-rwxr-xr-x[-rw-r--r--]src/com/android/settings/bluetooth/LocalBluetoothManager.java4
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothProfile.java10
-rw-r--r--src/com/android/settings/bluetooth/OppProfile.java6
-rw-r--r--src/com/android/settings/bluetooth/PanProfile.java10
-rwxr-xr-x[-rw-r--r--]src/com/android/settings/bluetooth/Utils.java8
-rw-r--r--src/com/android/settings/deviceinfo/Memory.java70
-rw-r--r--src/com/android/settings/deviceinfo/PercentageBarChart.java19
-rw-r--r--src/com/android/settings/deviceinfo/Status.java29
-rw-r--r--src/com/android/settings/deviceinfo/StorageMeasurement.java36
-rw-r--r--src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java18
-rw-r--r--src/com/android/settings/deviceinfo/UsageBarPreference.java6
-rw-r--r--src/com/android/settings/deviceinfo/UsbSettings.java131
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryChart.java17
-rw-r--r--src/com/android/settings/fuelgauge/PowerUsageSummary.java52
-rw-r--r--src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java118
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java221
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java50
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java24
-rw-r--r--src/com/android/settings/inputmethod/InputMethodConfig.java286
-rw-r--r--src/com/android/settings/inputmethod/InputMethodDialogReceiver.java32
-rw-r--r--src/com/android/settings/inputmethod/InputMethodPreference.java249
-rw-r--r--src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java125
-rw-r--r--src/com/android/settings/inputmethod/SpellCheckerUtils.java47
-rw-r--r--src/com/android/settings/inputmethod/SpellCheckersPreference.java27
-rw-r--r--src/com/android/settings/inputmethod/SpellCheckersSettings.java104
-rw-r--r--src/com/android/settings/inputmethod/UserDictionaryList.java113
-rw-r--r--src/com/android/settings/net/NetworkPolicyEditor.java183
-rw-r--r--src/com/android/settings/net/SummaryForAllUidLoader.java80
-rw-r--r--src/com/android/settings/nfc/NfcEnabler.java98
-rw-r--r--src/com/android/settings/nfc/ZeroClick.java107
-rw-r--r--src/com/android/settings/vpn/AuthenticationActor.java129
-rw-r--r--src/com/android/settings/vpn/L2tpEditor.java104
-rw-r--r--src/com/android/settings/vpn/L2tpIpsecEditor.java120
-rw-r--r--src/com/android/settings/vpn/L2tpIpsecPskEditor.java68
-rw-r--r--src/com/android/settings/vpn/PptpEditor.java73
-rw-r--r--src/com/android/settings/vpn/Util.java138
-rw-r--r--src/com/android/settings/vpn/VpnEditor.java261
-rw-r--r--src/com/android/settings/vpn/VpnProfileActor.java57
-rw-r--r--src/com/android/settings/vpn/VpnProfileEditor.java254
-rw-r--r--src/com/android/settings/vpn/VpnSettings.java1108
-rw-r--r--src/com/android/settings/vpn/VpnTypeSelection.java77
-rw-r--r--src/com/android/settings/vpn2/VpnDialog.java372
-rw-r--r--src/com/android/settings/vpn2/VpnProfile.java118
-rw-r--r--src/com/android/settings/vpn2/VpnSettings.java510
-rw-r--r--src/com/android/settings/widget/ChartAxis.java50
-rw-r--r--src/com/android/settings/widget/ChartGridView.java152
-rw-r--r--src/com/android/settings/widget/ChartNetworkSeriesView.java316
-rw-r--r--src/com/android/settings/widget/ChartSweepView.java518
-rw-r--r--src/com/android/settings/widget/ChartView.java119
-rw-r--r--src/com/android/settings/widget/DataUsageChartView.java545
-rw-r--r--src/com/android/settings/widget/InvertedChartAxis.java72
-rw-r--r--src/com/android/settings/wifi/AccessPoint.java65
-rw-r--r--src/com/android/settings/wifi/AdvancedSettings.java112
-rw-r--r--src/com/android/settings/wifi/AdvancedWifiSettings.java174
-rw-r--r--src/com/android/settings/wifi/WifiConfigController.java46
-rw-r--r--src/com/android/settings/wifi/WifiEnabler.java103
-rw-r--r--src/com/android/settings/wifi/WifiSettings.java319
-rw-r--r--src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java64
-rw-r--r--src/com/android/settings/wifi/p2p/WifiP2pDialog.java137
-rw-r--r--src/com/android/settings/wifi/p2p/WifiP2pEnabler.java144
-rw-r--r--src/com/android/settings/wifi/p2p/WifiP2pPeer.java104
-rw-r--r--src/com/android/settings/wifi/p2p/WifiP2pSettings.java256
149 files changed, 13722 insertions, 5511 deletions
diff --git a/src/com/android/settings/AccessibilityEnableScriptInjectionPreference.java b/src/com/android/settings/AccessibilityEnableScriptInjectionPreference.java
new file mode 100644
index 0000000..a9338ed
--- /dev/null
+++ b/src/com/android/settings/AccessibilityEnableScriptInjectionPreference.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.DialogPreference;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+/**
+ * Preference for enabling accessibility script injection. It displays a warning
+ * dialog before enabling the preference.
+ */
+public class AccessibilityEnableScriptInjectionPreference extends DialogPreference {
+
+ private boolean mInjectionAllowed;
+ private boolean mSendClickAccessibilityEvent;
+
+ public AccessibilityEnableScriptInjectionPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ updateSummary();
+ }
+
+ public void setInjectionAllowed(boolean injectionAllowed) {
+ if (mInjectionAllowed != injectionAllowed) {
+ mInjectionAllowed = injectionAllowed;
+ persistBoolean(injectionAllowed);
+ updateSummary();
+ }
+ }
+
+ public boolean isInjectionAllowed() {
+ return mInjectionAllowed;
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ View summaryView = view.findViewById(com.android.internal.R.id.summary);
+ sendAccessibilityEvent(summaryView);
+ }
+
+ private void sendAccessibilityEvent(View view) {
+ // Since the view is still not attached we create, populate,
+ // and send the event directly since we do not know when it
+ // will be attached and posting commands is not as clean.
+ AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getContext());
+ if (mSendClickAccessibilityEvent && accessibilityManager.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ view.onInitializeAccessibilityEvent(event);
+ view.dispatchPopulateAccessibilityEvent(event);
+ accessibilityManager.sendAccessibilityEvent(event);
+ }
+ mSendClickAccessibilityEvent = false;
+ }
+
+ @Override
+ protected void onClick() {
+ if (isInjectionAllowed()) {
+ setInjectionAllowed(false);
+ // Update the system setting only upon user action.
+ setSystemSetting(false);
+ mSendClickAccessibilityEvent = true;
+ } else {
+ super.onClick();
+ mSendClickAccessibilityEvent = false;
+ }
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getBoolean(index, false);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setInjectionAllowed(restoreValue
+ ? getPersistedBoolean(mInjectionAllowed)
+ : (Boolean) defaultValue);
+ }
+
+ @Override
+ protected void onDialogClosed(boolean result) {
+ setInjectionAllowed(result);
+ if (result) {
+ // Update the system setting only upon user action.
+ setSystemSetting(true);
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ return superState;
+ }
+ SavedState myState = new SavedState(superState);
+ myState.mInjectionAllowed = mInjectionAllowed;
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ setInjectionAllowed(myState.mInjectionAllowed);
+ }
+
+ private void updateSummary() {
+ setSummary(mInjectionAllowed
+ ? getContext().getString(R.string.accessibility_script_injection_allowed)
+ : getContext().getString(R.string.accessibility_script_injection_disallowed));
+ }
+
+ private void setSystemSetting(boolean enabled) {
+ Settings.Secure.putInt(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, enabled ? 1 : 0);
+ }
+
+ private static class SavedState extends BaseSavedState {
+ private boolean mInjectionAllowed;
+
+ public SavedState(Parcel source) {
+ super(source);
+ mInjectionAllowed = (source.readInt() == 1);
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ super.writeToParcel(parcel, flags);
+ parcel.writeInt(mInjectionAllowed ? 1 : 0);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @SuppressWarnings("all")
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/src/com/android/settings/AccessibilitySettings.java b/src/com/android/settings/AccessibilitySettings.java
index 826410d..e1b44e7 100644
--- a/src/com/android/settings/AccessibilitySettings.java
+++ b/src/com/android/settings/AccessibilitySettings.java
@@ -16,366 +16,515 @@
package com.android.settings;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
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.provider.Settings;
import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.widget.Switch;
+import android.widget.TextView;
+import com.android.internal.content.PackageMonitor;
+import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener;
+
+import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Activity with the accessibility settings.
*/
public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
Preference.OnPreferenceChangeListener {
+
private static final String DEFAULT_SCREENREADER_MARKET_LINK =
"market://search?q=pname:com.google.android.marvin.talkback";
- private final String TOGGLE_ACCESSIBILITY_CHECKBOX =
- "toggle_accessibility_service_checkbox";
+ private static final float LARGE_FONT_SCALE = 1.3f;
+
+ private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
+
+ // Timeout before we update the services if packages are added/removed since
+ // the AccessibilityManagerService has to do that processing first to generate
+ // the AccessibilityServiceInfo we need for proper presentation.
+ private static final long DELAY_UPDATE_SERVICES_PREFERENCES_MILLIS = 1000;
- private static final String ACCESSIBILITY_SERVICES_CATEGORY =
- "accessibility_services_category";
+ private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
- private static final String TOGGLE_ACCESSIBILITY_SCRIPT_INJECTION_CHECKBOX =
- "toggle_accessibility_script_injection_checkbox";
+ private static final String KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE =
+ "key_accessibility_tutorial_launched_once";
- private static final String POWER_BUTTON_CATEGORY =
- "power_button_category";
+ private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
+ "key_install_accessibility_service_offered_once";
- private static final String POWER_BUTTON_ENDS_CALL_CHECKBOX =
- "power_button_ends_call";
+ // Preference categories
+ private static final String SERVICES_CATEGORY = "services_category";
+ private static final String SYSTEM_CATEGORY = "system_category";
- private final String KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX =
- "key_toggle_accessibility_service_checkbox";
+ // Preferences
+ private static final String TOGGLE_LARGE_TEXT_PREFERENCE = "toggle_large_text_preference";
+ private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE =
+ "toggle_power_button_ends_call_preference";
+ private static final String TOGGLE_TOUCH_EXPLORATION_PREFERENCE =
+ "toggle_touch_exploration_preference";
+ private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
+ "select_long_press_timeout_preference";
+ private static final String TOGGLE_SCRIPT_INJECTION_PREFERENCE =
+ "toggle_script_injection_preference";
- private final String KEY_LONG_PRESS_TIMEOUT_LIST_PREFERENCE =
- "long_press_timeout_list_preference";
+ // Extras passed to sub-fragments.
+ private static final String EXTRA_PREFERENCE_KEY = "preference_key";
+ private static final String EXTRA_CHECKED = "checked";
+ private static final String EXTRA_TITLE = "title";
+ private static final String EXTRA_SUMMARY = "summary";
+ private static final String EXTRA_WARNING_MESSAGE = "warning_message";
+ private static final String EXTRA_SETTINGS_TITLE = "settings_title";
+ private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
+ // Dialog IDs.
private static final int DIALOG_ID_DISABLE_ACCESSIBILITY = 1;
- private static final int DIALOG_ID_ENABLE_SCRIPT_INJECTION = 2;
- private static final int DIALOG_ID_ENABLE_ACCESSIBILITY_SERVICE = 3;
- private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 4;
+ private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 2;
+
+ // Auxiliary members.
+ private final SimpleStringSplitter mStringColonSplitter =
+ new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
+
+ private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
+ new HashMap<String, String>();
+
+ private final Configuration mCurConfig = new Configuration();
- private CheckBoxPreference mToggleAccessibilityCheckBox;
- private CheckBoxPreference mToggleScriptInjectionCheckBox;
- private CheckBoxPreference mToggleAccessibilityServiceCheckBox;
+ private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
- private PreferenceCategory mPowerButtonCategory;
- private CheckBoxPreference mPowerButtonEndsCallCheckBox;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void dispatchMessage(Message msg) {
+ super.dispatchMessage(msg);
+ updateServicesPreferences(mToggleAccessibilitySwitch.isChecked());
+ }
+ };
- private PreferenceGroup mAccessibilityServicesCategory;
+ // Preference controls.
+ private ToggleSwitch mToggleAccessibilitySwitch;
- private ListPreference mLongPressTimeoutListPreference;
+ private PreferenceCategory mServicesCategory;
+ private PreferenceCategory mSystemsCategory;
- private Map<String, ServiceInfo> mAccessibilityServices =
- new LinkedHashMap<String, ServiceInfo>();
+ private CheckBoxPreference mToggleLargeTextPreference;
+ private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
+ private Preference mToggleTouchExplorationPreference;
+ private ListPreference mSelectLongPressTimeoutPreference;
+ private AccessibilityEnableScriptInjectionPreference mToggleScriptInjectionPreference;
- private TextUtils.SimpleStringSplitter mStringColonSplitter =
- new TextUtils.SimpleStringSplitter(':');
+ private int mLongPressTimeoutDefault;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
-
addPreferencesFromResource(R.xml.accessibility_settings);
+ initializeAllPreferences();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ final boolean accessibilityEnabled = mToggleAccessibilitySwitch.isChecked();
+ updateAllPreferences(accessibilityEnabled);
+ if (accessibilityEnabled) {
+ offerInstallAccessibilitySerivceOnce();
+ }
+ mSettingsPackageMonitor.register(getActivity(), false);
+ }
+
+ @Override
+ public void onPause() {
+ mSettingsPackageMonitor.unregister();
+ super.onPause();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ addToggleAccessibilitySwitch();
+ super.onViewCreated(view, savedInstanceState);
+ }
+
+ @Override
+ public void onDestroyView() {
+ removeToggleAccessibilitySwitch();
+ super.onDestroyView();
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mSelectLongPressTimeoutPreference) {
+ String stringValue = (String) newValue;
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
+ mSelectLongPressTimeoutPreference.setSummary(
+ mLongPressTimeoutValuetoTitleMap.get(stringValue));
+ return true;
+ }
+ return false;
+ }
- mAccessibilityServicesCategory =
- (PreferenceGroup) findPreference(ACCESSIBILITY_SERVICES_CATEGORY);
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ final String key = preference.getKey();
+ if (mToggleLargeTextPreference == preference) {
+ handleToggleLargeTextPreferenceClick();
+ return true;
+ } else if (mTogglePowerButtonEndsCallPreference == preference) {
+ handleTogglePowerButtonEndsCallPreferenceClick();
+ return true;
+ }
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
- mToggleAccessibilityCheckBox = (CheckBoxPreference) findPreference(
- TOGGLE_ACCESSIBILITY_CHECKBOX);
+ private void handleToggleLargeTextPreferenceClick() {
+ try {
+ mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
+ ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
- mToggleScriptInjectionCheckBox = (CheckBoxPreference) findPreference(
- TOGGLE_ACCESSIBILITY_SCRIPT_INJECTION_CHECKBOX);
+ private void handleTogglePowerButtonEndsCallPreferenceClick() {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+ (mTogglePowerButtonEndsCallPreference.isChecked()
+ ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
+ : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
+ }
- mPowerButtonCategory = (PreferenceCategory) findPreference(POWER_BUTTON_CATEGORY);
- mPowerButtonEndsCallCheckBox = (CheckBoxPreference) findPreference(
- POWER_BUTTON_ENDS_CALL_CHECKBOX);
+ private void addToggleAccessibilitySwitch() {
+ mToggleAccessibilitySwitch = createAndAddActionBarToggleSwitch(getActivity());
+ final boolean checked = (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1);
+ mToggleAccessibilitySwitch.setChecked(checked);
+ mToggleAccessibilitySwitch.setOnBeforeCheckedChangeListener(
+ new OnBeforeCheckedChangeListener() {
+ @Override
+ public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
+ if (!checked) {
+ toggleSwitch.setCheckedInternal(true);
+ showDialog(DIALOG_ID_DISABLE_ACCESSIBILITY);
+ return true;
+ }
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 1);
+ updateAllPreferences(true);
+ offerInstallAccessibilitySerivceOnce();
+ return false;
+ }
+ });
+ }
- mLongPressTimeoutListPreference = (ListPreference) findPreference(
- KEY_LONG_PRESS_TIMEOUT_LIST_PREFERENCE);
+ public void removeToggleAccessibilitySwitch() {
+ mToggleAccessibilitySwitch.setOnBeforeCheckedChangeListener(null);
+ getActivity().getActionBar().setCustomView(null);
+ }
- // set the accessibility script injection category
- boolean scriptInjectionEnabled = (Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
- mToggleScriptInjectionCheckBox.setChecked(scriptInjectionEnabled);
- mToggleScriptInjectionCheckBox.setEnabled(true);
+ private void initializeAllPreferences() {
+ // The basic logic here is if accessibility is not enabled all accessibility
+ // settings will have no effect but still their selected state should be kept
+ // unchanged, so the user can see what settings will be enabled when turning
+ // on accessibility.
+
+ final boolean accessibilityEnabled = (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1);
+
+ mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
+ mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
+
+ // Large text.
+ mToggleLargeTextPreference =
+ (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
+ if (accessibilityEnabled) {
+ try {
+ mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
+ }
+ // Power button ends calls.
+ mTogglePowerButtonEndsCallPreference =
+ (CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
&& Utils.isVoiceCapable(getActivity())) {
- 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 =
+ if (accessibilityEnabled) {
+ final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+ Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
+ final boolean powerButtonEndsCall =
(incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
- mPowerButtonEndsCallCheckBox.setChecked(powerButtonCheckboxEnabled);
- mPowerButtonEndsCallCheckBox.setEnabled(true);
+ mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall);
+ }
} else {
- // No POWER key on the current device or no voice capability;
- // this entire category is irrelevant.
- getPreferenceScreen().removePreference(mPowerButtonCategory);
+ mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference);
}
- mLongPressTimeoutListPreference.setOnPreferenceChangeListener(this);
+ // Touch exploration enabled.
+ mToggleTouchExplorationPreference = findPreference(TOGGLE_TOUCH_EXPLORATION_PREFERENCE);
+ final boolean touchExplorationEnabled = (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1);
+ if (touchExplorationEnabled) {
+ mToggleTouchExplorationPreference.setSummary(
+ getString(R.string.accessibility_service_state_on));
+ mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, true);
+ } else {
+ mToggleTouchExplorationPreference.setSummary(
+ getString(R.string.accessibility_service_state_off));
+ mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, false);
+ }
+
+ // Long press timeout.
+ mSelectLongPressTimeoutPreference =
+ (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE);
+ mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this);
+ if (mLongPressTimeoutValuetoTitleMap.size() == 0) {
+ String[] timeoutValues = getResources().getStringArray(
+ R.array.long_press_timeout_selector_values);
+ mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
+ String[] timeoutTitles = getResources().getStringArray(
+ R.array.long_press_timeout_selector_titles);
+ final int timeoutValueCount = timeoutValues.length;
+ for (int i = 0; i < timeoutValueCount; i++) {
+ mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]);
+ }
+ }
+ if (accessibilityEnabled) {
+ final int longPressTimeout = Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
+ String value = String.valueOf(longPressTimeout);
+ mSelectLongPressTimeoutPreference.setValue(value);
+ mSelectLongPressTimeoutPreference.setSummary(
+ mLongPressTimeoutValuetoTitleMap.get(value));
+ } else {
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.LONG_PRESS_TIMEOUT,
+ mLongPressTimeoutDefault);
+ }
+
+ // Script injection.
+ mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference)
+ findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE);
+ if (accessibilityEnabled) {
+ final boolean scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
+ mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed);
+ }
}
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- addAccessibilitServicePreferences();
+ private void updateAllPreferences(boolean accessibilityEnabled) {
+ updateServicesPreferences(accessibilityEnabled);
+ updateSystemPreferences(accessibilityEnabled);
+ }
- final HashSet<String> enabled = new HashSet<String>();
+ private void updateServicesPreferences(boolean accessibilityEnabled) {
+ // Since services category is auto generated we have to do a pass
+ // to generate it since services can come and go and then based on
+ // the global accessibility state to decided whether it is enabled.
+
+ // Generate.
+ mServicesCategory.removeAll();
+
+ AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
+
+ List<AccessibilityServiceInfo> installedServices =
+ accessibilityManager.getInstalledAccessibilityServiceList();
+
+ Set<ComponentName> enabledComponentNames = new HashSet<ComponentName>();
String settingValue = Settings.Secure.getString(getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
- TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ SimpleStringSplitter splitter = mStringColonSplitter;
splitter.setString(settingValue);
while (splitter.hasNext()) {
- enabled.add(splitter.next());
+ enabledComponentNames.add(ComponentName.unflattenFromString(splitter.next()));
}
}
- Map<String, ServiceInfo> accessibilityServices = mAccessibilityServices;
+ for (int i = 0, count = installedServices.size(); i < count; ++i) {
+ AccessibilityServiceInfo info = installedServices.get(i);
+ String key = info.getId();
- for (String key : accessibilityServices.keySet()) {
- CheckBoxPreference preference = (CheckBoxPreference) findPreference(key);
- if (preference != null) {
- preference.setChecked(enabled.contains(key));
- }
- }
+ PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
+ getActivity());
+ String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
- int serviceState = Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0);
+ ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
+ ComponentName componentName = new ComponentName(serviceInfo.packageName,
+ serviceInfo.name);
- if (!accessibilityServices.isEmpty()) {
- if (serviceState == 1) {
- mToggleAccessibilityCheckBox.setChecked(true);
- if (savedInstanceState != null) {
- restoreInstanceState(savedInstanceState);
- }
+ preference.setKey(componentName.flattenToString());
+
+ preference.setTitle(title);
+ final boolean enabled = enabledComponentNames.contains(componentName);
+ if (enabled) {
+ preference.setSummary(getString(R.string.accessibility_service_state_on));
} else {
- setAccessibilityServicePreferencesState(false);
+ preference.setSummary(getString(R.string.accessibility_service_state_off));
}
- mToggleAccessibilityCheckBox.setEnabled(true);
- } else {
- if (serviceState == 1) {
- // no service and accessibility is enabled => disable
- Settings.Secure.putInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0);
- }
- mToggleAccessibilityCheckBox.setEnabled(false);
- // Notify user that they do not have any accessibility apps
- // installed and direct them to Market to get TalkBack
- showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
- }
- super.onActivityCreated(savedInstanceState);
- }
+ preference.setOrder(i);
+ preference.setFragment(ToggleAccessibilityServiceFragment.class.getName());
+ preference.setPersistent(true);
- @Override
- public void onPause() {
- super.onPause();
+ Bundle extras = preference.getExtras();
+ extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
+ extras.putBoolean(EXTRA_CHECKED, enabled);
+ extras.putString(EXTRA_TITLE, title);
- persistEnabledAccessibilityServices();
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- if (mToggleAccessibilityServiceCheckBox != null) {
- outState.putString(KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX,
- mToggleAccessibilityServiceCheckBox.getKey());
- }
- }
+ String description = info.getDescription();
+ if (TextUtils.isEmpty(description)) {
+ description = getString(R.string.accessibility_service_default_description);
+ }
+ extras.putString(EXTRA_SUMMARY, description);
+
+ extras.putString(EXTRA_WARNING_MESSAGE, getString(
+ R.string.accessibility_service_security_warning,
+ info.getResolveInfo().loadLabel(getPackageManager())));
+
+ String settingsClassName = info.getSettingsActivityName();
+ if (!TextUtils.isEmpty(settingsClassName)) {
+ extras.putString(EXTRA_SETTINGS_TITLE,
+ getString(R.string.accessibility_menu_item_settings));
+ extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
+ new ComponentName(info.getResolveInfo().serviceInfo.packageName,
+ settingsClassName).flattenToString());
+ }
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (preference == mLongPressTimeoutListPreference) {
- int intValue = Integer.parseInt((String) newValue);
- Settings.Secure.putInt(getContentResolver(),
- Settings.Secure.LONG_PRESS_TIMEOUT, intValue);
- return true;
+ mServicesCategory.addPreference(preference);
}
- return false;
- }
- /**
- * Restores the instance state from <code>savedInstanceState</code>.
- */
- private void restoreInstanceState(Bundle savedInstanceState) {
- String key = savedInstanceState.getString(KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX);
- if (key != null) {
- Preference preference = findPreference(key);
- if (!(preference instanceof CheckBoxPreference)) {
- throw new IllegalArgumentException(
- KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX
- + " must be mapped to an instance of a "
- + CheckBoxPreference.class.getName());
- }
- mToggleAccessibilityServiceCheckBox = (CheckBoxPreference) preference;
- }
+ // Update enabled state.
+ mServicesCategory.setEnabled(accessibilityEnabled);
}
- /**
- * Sets the state of the preferences for enabling/disabling
- * AccessibilityServices.
- *
- * @param isEnabled If to enable or disable the preferences.
- */
- private void setAccessibilityServicePreferencesState(boolean isEnabled) {
- if (mAccessibilityServicesCategory == null) {
- return;
+ private void updateSystemPreferences(boolean accessibilityEnabled) {
+ // The basic logic here is if accessibility is not enabled all accessibility
+ // settings will have no effect but still their selected state should be kept
+ // unchanged, so the user can see what settings will be enabled when turning
+ // on accessibility.
+
+ // Large text.
+ mToggleLargeTextPreference.setEnabled(accessibilityEnabled);
+ if (accessibilityEnabled) {
+ mCurConfig.fontScale =
+ mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
+ } else {
+ mCurConfig.fontScale = 1;
}
-
- int count = mAccessibilityServicesCategory.getPreferenceCount();
- for (int i = 0; i < count; i++) {
- Preference pref = mAccessibilityServicesCategory.getPreference(i);
- pref.setEnabled(isEnabled);
+ try {
+ ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
+ } catch (RemoteException re) {
+ /* ignore */
}
- mToggleScriptInjectionCheckBox.setEnabled(isEnabled);
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- final String key = preference.getKey();
-
- if (TOGGLE_ACCESSIBILITY_CHECKBOX.equals(key)) {
- 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.
+ // Power button ends calls.
+ if (mTogglePowerButtonEndsCallPreference != null) {
+ mTogglePowerButtonEndsCallPreference.setEnabled(accessibilityEnabled);
+ final int powerButtonEndsCall;
+ if (accessibilityEnabled) {
+ powerButtonEndsCall = mTogglePowerButtonEndsCallPreference.isChecked()
+ ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
+ : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
+ } else {
+ powerButtonEndsCall = Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
+ }
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 (TOGGLE_ACCESSIBILITY_SCRIPT_INJECTION_CHECKBOX.equals(key)) {
- handleToggleAccessibilityScriptInjection((CheckBoxPreference) preference);
- } else if (preference instanceof CheckBoxPreference) {
- handleEnableAccessibilityServiceStateChange((CheckBoxPreference) preference);
+ powerButtonEndsCall);
}
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
-
- /**
- * Handles the change of the accessibility enabled setting state.
- *
- * @param preference The preference for enabling/disabling accessibility.
- */
- private void handleEnableAccessibilityStateChange(CheckBoxPreference preference) {
- if (preference.isChecked()) {
- Settings.Secure.putInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 1);
- setAccessibilityServicePreferencesState(true);
+ // Touch exploration enabled.
+ mToggleTouchExplorationPreference.setEnabled(accessibilityEnabled);
+ final boolean touchExplorationEnabled = (Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1);
+ if (touchExplorationEnabled) {
+ mToggleTouchExplorationPreference.setSummary(
+ getString(R.string.accessibility_service_state_on));
+ mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, true);
} else {
- // set right enabled state since the user may press back
- preference.setChecked(true);
- showDialog(DIALOG_ID_DISABLE_ACCESSIBILITY);
+ mToggleTouchExplorationPreference.setSummary(
+ getString(R.string.accessibility_service_state_off));
+ mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, false);
}
- }
- /**
- * Handles the change of the accessibility script injection setting state.
- *
- * @param preference The preference for enabling/disabling accessibility script injection.
- */
- private void handleToggleAccessibilityScriptInjection(CheckBoxPreference preference) {
- if (preference.isChecked()) {
- // set right enabled state since the user may press back
- preference.setChecked(false);
- showDialog(DIALOG_ID_ENABLE_SCRIPT_INJECTION);
+ // Long press timeout.
+ mSelectLongPressTimeoutPreference.setEnabled(accessibilityEnabled);
+ final int longPressTimeout;
+ if (accessibilityEnabled) {
+ String value = mSelectLongPressTimeoutPreference.getValue();
+ longPressTimeout = (value != null) ? Integer.parseInt(value) : mLongPressTimeoutDefault;
} else {
- Settings.Secure.putInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0);
+ longPressTimeout = mLongPressTimeoutDefault;
}
- }
-
- /**
- * Handles the change of the preference for enabling/disabling an AccessibilityService.
- *
- * @param preference The preference.
- */
- private void handleEnableAccessibilityServiceStateChange(CheckBoxPreference preference) {
- if (preference.isChecked()) {
- mToggleAccessibilityServiceCheckBox = preference;
- // set right enabled state since the user may press back
- preference.setChecked(false);
- showDialog(DIALOG_ID_ENABLE_ACCESSIBILITY_SERVICE);
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.LONG_PRESS_TIMEOUT,
+ longPressTimeout);
+ String value = mSelectLongPressTimeoutPreference.getValue();
+ mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
+
+ // Script injection.
+ mToggleScriptInjectionPreference.setEnabled(accessibilityEnabled);
+ final boolean scriptInjectionAllowed;
+ if (accessibilityEnabled) {
+ scriptInjectionAllowed = mToggleScriptInjectionPreference.isInjectionAllowed();
} else {
- persistEnabledAccessibilityServices();
- }
- }
-
- /**
- * Persists the Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES setting.
- * The AccessibilityManagerService watches this property and manages the
- * AccessibilityServices.
- */
- private void persistEnabledAccessibilityServices() {
- StringBuilder builder = new StringBuilder(256);
-
- int firstEnabled = -1;
- for (String key : mAccessibilityServices.keySet()) {
- CheckBoxPreference preference = (CheckBoxPreference) findPreference(key);
- if (preference.isChecked()) {
- builder.append(key);
- builder.append(':');
- }
+ scriptInjectionAllowed = false;
}
-
- Settings.Secure.putString(getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, builder.toString());
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+ scriptInjectionAllowed ? 1 : 0);
}
- /**
- * Adds {@link CheckBoxPreference} for enabling or disabling an accessibility services.
- */
- private void addAccessibilitServicePreferences() {
- AccessibilityManager accessibilityManager =
- (AccessibilityManager) getSystemService(Service.ACCESSIBILITY_SERVICE);
-
- List<ServiceInfo> installedServices = accessibilityManager.getAccessibilityServiceList();
-
- if (installedServices.isEmpty()) {
- getPreferenceScreen().removePreference(mAccessibilityServicesCategory);
+ private void offerInstallAccessibilitySerivceOnce() {
+ if (mServicesCategory.getPreferenceCount() > 0) {
return;
}
-
- getPreferenceScreen().addPreference(mAccessibilityServicesCategory);
-
- for (int i = 0, count = installedServices.size(); i < count; ++i) {
- ServiceInfo serviceInfo = installedServices.get(i);
- String key = serviceInfo.packageName + "/" + serviceInfo.name;
-
- if (mAccessibilityServices.put(key, serviceInfo) == null) {
- CheckBoxPreference preference = new CheckBoxPreference(getActivity());
- preference.setKey(key);
- preference.setTitle(serviceInfo.loadLabel(getActivity().getPackageManager()));
- mAccessibilityServicesCategory.addPreference(preference);
- }
+ SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
+ final boolean offerInstallService = !preferences.getBoolean(
+ KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
+ if (offerInstallService) {
+ preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
+ true).commit();
+ // Notify user that they do not have any accessibility
+ // services installed and direct them to Market to get TalkBack.
+ showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
}
}
@@ -384,56 +533,28 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
switch (dialogId) {
case DIALOG_ID_DISABLE_ACCESSIBILITY:
return (new AlertDialog.Builder(getActivity()))
- .setTitle(android.R.string.dialog_alert_title)
+ .setTitle(R.string.accessibility_disable_warning_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(getResources().
- getString(R.string.accessibility_service_disable_warning))
+ getString(R.string.accessibility_disable_warning_summary))
.setCancelable(true)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0);
- mToggleAccessibilityCheckBox.setChecked(false);
- setAccessibilityServicePreferencesState(false);
+ mToggleAccessibilitySwitch.setCheckedInternal(
+ false);
+ updateAllPreferences(false);
}
})
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- case DIALOG_ID_ENABLE_SCRIPT_INJECTION:
- return new AlertDialog.Builder(getActivity())
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(getActivity().getString(
- R.string.accessibility_script_injection_security_warning))
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- Settings.Secure.putInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 1);
- mToggleScriptInjectionCheckBox.setChecked(true);
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- case DIALOG_ID_ENABLE_ACCESSIBILITY_SERVICE:
- return new AlertDialog.Builder(getActivity())
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(getResources().getString(
- R.string.accessibility_service_security_warning,
- mAccessibilityServices.get(mToggleAccessibilityServiceCheckBox.getKey())
- .applicationInfo.loadLabel(getActivity().getPackageManager())))
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- mToggleAccessibilityServiceCheckBox.setChecked(true);
- persistEnabledAccessibilityServices();
- }
- })
- .setNegativeButton(android.R.string.cancel, null)
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mToggleAccessibilitySwitch.setCheckedInternal(
+ true);
+ }
+ })
.create();
case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
return new AlertDialog.Builder(getActivity())
@@ -445,9 +566,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
// dismiss the dialog before launching the activity otherwise
// the dialog removal occurs after onSaveInstanceState which
// triggers an exception
- dialog.dismiss();
+ removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
String screenreaderMarketLink = SystemProperties.get(
- "ro.screenreader.market", DEFAULT_SCREENREADER_MARKET_LINK);
+ SYSTEM_PROPERTY_MARKET_URL,
+ DEFAULT_SCREENREADER_MARKET_LINK);
Uri marketUri = Uri.parse(screenreaderMarketLink);
Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
startActivity(marketIntent);
@@ -459,4 +581,309 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
return null;
}
}
+
+ private class SettingsPackageMonitor extends PackageMonitor {
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ Message message = mHandler.obtainMessage();
+ mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_PREFERENCES_MILLIS);
+ }
+
+ @Override
+ public void onPackageAppeared(String packageName, int reason) {
+ Message message = mHandler.obtainMessage();
+ mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_PREFERENCES_MILLIS);
+ }
+
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ Message message = mHandler.obtainMessage();
+ mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_PREFERENCES_MILLIS);
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ Message message = mHandler.obtainMessage();
+ mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_PREFERENCES_MILLIS);
+ }
+ }
+
+ private static ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
+ ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
+ final int padding = activity.getResources().getDimensionPixelSize(
+ R.dimen.action_bar_switch_padding);
+ toggleSwitch.setPadding(0, 0, padding, 0);
+ activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM);
+ activity.getActionBar().setCustomView(toggleSwitch,
+ new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT));
+ return toggleSwitch;
+ }
+
+ public static class ToggleSwitch extends Switch {
+
+ private OnBeforeCheckedChangeListener mOnBeforeListener;
+
+ public static interface OnBeforeCheckedChangeListener {
+ public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
+ }
+
+ public ToggleSwitch(Context context) {
+ super(context);
+ }
+
+ public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
+ mOnBeforeListener = listener;
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (mOnBeforeListener != null
+ && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
+ return;
+ }
+ super.setChecked(checked);
+ }
+
+ public void setCheckedInternal(boolean checked) {
+ super.setChecked(checked);
+ }
+ }
+
+ public static class ToggleAccessibilityServiceFragment extends TogglePreferenceFragment {
+ @Override
+ public void onPreferenceToggled(String preferenceKey, boolean enabled) {
+ String enabledServices = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ if (enabledServices == null) {
+ enabledServices = "";
+ }
+ final int length = enabledServices.length();
+ if (enabled) {
+ if (enabledServices.contains(preferenceKey)) {
+ return;
+ }
+ if (length == 0) {
+ enabledServices += preferenceKey;
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
+ } else if (length > 0) {
+ enabledServices += ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR + preferenceKey;
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
+ }
+ } else {
+ final int index = enabledServices.indexOf(preferenceKey);
+ if (index == 0) {
+ enabledServices = enabledServices.replace(preferenceKey, "");
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
+ } else if (index > 0) {
+ enabledServices = enabledServices.replace(
+ ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR + preferenceKey, "");
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
+ }
+ }
+ }
+ }
+
+ public static class ToggleTouchExplorationFragment extends TogglePreferenceFragment {
+ @Override
+ public void onPreferenceToggled(String preferenceKey, boolean enabled) {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0);
+ if (enabled) {
+ SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
+ final boolean launchAccessibilityTutorial = !preferences.getBoolean(
+ KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE, false);
+ if (launchAccessibilityTutorial) {
+ preferences.edit().putBoolean(KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE,
+ true).commit();
+ Intent intent = new Intent(AccessibilityTutorialActivity.ACTION);
+ getActivity().startActivity(intent);
+ }
+ }
+ }
+ }
+
+ private abstract static class TogglePreferenceFragment extends SettingsPreferenceFragment
+ implements DialogInterface.OnClickListener {
+
+ private static final int DIALOG_ID_WARNING = 1;
+
+ private String mPreferenceKey;
+
+ private ToggleSwitch mToggleSwitch;
+
+ private CharSequence mWarningMessage;
+ private Preference mSummaryPreference;
+
+ private CharSequence mSettingsTitle;
+ private Intent mSettingsIntent;
+
+ // TODO: Showing sub-sub fragment does not handle the activity title
+ // so we do it but this is wrong. Do a real fix when there is time.
+ private CharSequence mOldActivityTitle;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
+ getActivity());
+ setPreferenceScreen(preferenceScreen);
+ mSummaryPreference = new Preference(getActivity()) {
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ TextView summaryView = (TextView) view.findViewById(R.id.summary);
+ summaryView.setText(getSummary());
+ sendAccessibilityEvent(summaryView);
+ }
+
+ private void sendAccessibilityEvent(View view) {
+ // Since the view is still not attached we create, populate,
+ // and send the event directly since we do not know when it
+ // will be attached and posting commands is not as clean.
+ AccessibilityManager accessibilityManager =
+ AccessibilityManager.getInstance(getActivity());
+ if (accessibilityManager.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ view.onInitializeAccessibilityEvent(event);
+ view.dispatchPopulateAccessibilityEvent(event);
+ accessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+ };
+ mSummaryPreference.setPersistent(false);
+ mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
+ preferenceScreen.addPreference(mSummaryPreference);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ installActionBarToggleSwitch();
+ processArguments();
+ getListView().setDivider(null);
+ getListView().setEnabled(false);
+ }
+
+ @Override
+ public void onDestroyView() {
+ getActivity().getActionBar().setCustomView(null);
+ if (mOldActivityTitle != null) {
+ getActivity().getActionBar().setTitle(mOldActivityTitle);
+ }
+ mToggleSwitch.setOnBeforeCheckedChangeListener(null);
+ super.onDestroyView();
+ }
+
+ public abstract void onPreferenceToggled(String preferenceKey, boolean value);
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ MenuItem menuItem = menu.add(mSettingsTitle);
+ menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ menuItem.setIntent(mSettingsIntent);
+ }
+
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ switch (dialogId) {
+ case DIALOG_ID_WARNING:
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(mWarningMessage)
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ // OK, we got the user consent so set checked.
+ mToggleSwitch.setCheckedInternal(true);
+ onPreferenceToggled(mPreferenceKey, true);
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ onPreferenceToggled(mPreferenceKey, false);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private void installActionBarToggleSwitch() {
+ mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
+ mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
+ @Override
+ public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
+ if (checked) {
+ if (!TextUtils.isEmpty(mWarningMessage)) {
+ toggleSwitch.setCheckedInternal(false);
+ showDialog(DIALOG_ID_WARNING);
+ return true;
+ }
+ onPreferenceToggled(mPreferenceKey, true);
+ } else {
+ onPreferenceToggled(mPreferenceKey, false);
+ }
+ return false;
+ }
+ });
+ }
+
+ private void processArguments() {
+ Bundle arguments = getArguments();
+
+ // Key.
+ mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
+
+ // Enabled.
+ final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
+ mToggleSwitch.setCheckedInternal(enabled);
+
+ // Title.
+ PreferenceActivity activity = (PreferenceActivity) getActivity();
+ if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
+ mOldActivityTitle = getActivity().getTitle();
+ String title = arguments.getString(EXTRA_TITLE);
+ getActivity().getActionBar().setTitle(arguments.getCharSequence(EXTRA_TITLE));
+ }
+
+ // Summary.
+ String summary = arguments.getString(EXTRA_SUMMARY);
+ mSummaryPreference.setSummary(summary);
+
+ // Settings title and intent.
+ String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE);
+ String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME);
+ if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
+ Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
+ ComponentName.unflattenFromString(settingsComponentName.toString()));
+ if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
+ mSettingsTitle = settingsTitle;
+ mSettingsIntent = settingsIntent;
+ setHasOptionsMenu(true);
+ }
+ }
+
+ // Waring message.
+ mWarningMessage = arguments.getCharSequence(
+ AccessibilitySettings.EXTRA_WARNING_MESSAGE);
+ }
+ }
}
diff --git a/src/com/android/settings/AccessibilityTutorialActivity.java b/src/com/android/settings/AccessibilityTutorialActivity.java
new file mode 100644
index 0000000..da8350c
--- /dev/null
+++ b/src/com/android/settings/AccessibilityTutorialActivity.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * 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.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.GridView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.ViewAnimator;
+
+import com.android.settings.R;
+
+import java.util.List;
+
+/**
+ * This class provides a short tutorial that introduces the user to the features
+ * available in Touch Exploration.
+ */
+public class AccessibilityTutorialActivity extends Activity {
+
+ /** Intent action for launching this activity. */
+ public static final String ACTION = "android.settings.ACCESSIBILITY_TUTORIAL";
+
+ /** Instance state saving constant for the active module. */
+ private static final String KEY_ACTIVE_MODULE = "active_module";
+
+ /** The index of the module to show when first opening the tutorial. */
+ private static final int DEFAULT_MODULE = 0;
+
+ /** View animator for switching between modules. */
+ private ViewAnimator mViewAnimator;
+
+ private AccessibilityManager mAccessibilityManager;
+
+ /** Should touch exploration be disabled when this activity is paused? */
+ private boolean mDisableOnPause;
+
+ private final AnimationListener mInAnimationListener = new AnimationListener() {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ final int index = mViewAnimator.getDisplayedChild();
+ final TutorialModule module = (TutorialModule) mViewAnimator.getChildAt(index);
+
+ activateModule(module);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ // Do nothing.
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Animation inAnimation = AnimationUtils.loadAnimation(this,
+ android.R.anim.slide_in_left);
+ inAnimation.setAnimationListener(mInAnimationListener);
+
+ final Animation outAnimation = AnimationUtils.loadAnimation(this,
+ android.R.anim.slide_in_left);
+
+ mViewAnimator = new ViewAnimator(this);
+ mViewAnimator.setInAnimation(inAnimation);
+ mViewAnimator.setOutAnimation(outAnimation);
+ mViewAnimator.addView(new TouchTutorialModule1(this, this));
+ mViewAnimator.addView(new TouchTutorialModule2(this, this));
+
+ setContentView(mViewAnimator);
+
+ mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
+
+ if (savedInstanceState != null) {
+ show(savedInstanceState.getInt(KEY_ACTIVE_MODULE, DEFAULT_MODULE));
+ } else {
+ show(DEFAULT_MODULE);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ final ContentResolver cr = getContentResolver();
+
+ if (Settings.Secure.getInt(cr, Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 0) {
+ Settings.Secure.putInt(cr, Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1);
+ mDisableOnPause = true;
+ } else {
+ mDisableOnPause = false;
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ if (mDisableOnPause) {
+ final ContentResolver cr = getContentResolver();
+ Settings.Secure.putInt(cr, Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putInt(KEY_ACTIVE_MODULE, mViewAnimator.getDisplayedChild());
+ }
+
+ private void activateModule(TutorialModule module) {
+ module.activate();
+ }
+
+ private void deactivateModule(TutorialModule module) {
+ mAccessibilityManager.interrupt();
+ mViewAnimator.setOnKeyListener(null);
+ module.deactivate();
+ }
+
+ private void interrupt() {
+ mAccessibilityManager.interrupt();
+ }
+
+ private void next() {
+ show(mViewAnimator.getDisplayedChild() + 1);
+ }
+
+ private void previous() {
+ show(mViewAnimator.getDisplayedChild() - 1);
+ }
+
+ private void show(int which) {
+ if ((which < 0) || (which >= mViewAnimator.getChildCount())) {
+ return;
+ }
+
+ mAccessibilityManager.interrupt();
+
+ final int displayedIndex = mViewAnimator.getDisplayedChild();
+ final TutorialModule displayedView = (TutorialModule) mViewAnimator.getChildAt(
+ displayedIndex);
+ deactivateModule(displayedView);
+
+ mViewAnimator.setDisplayedChild(which);
+ }
+
+ /**
+ * Loads application labels and icons.
+ */
+ private static class AppsAdapter extends ArrayAdapter<ResolveInfo> {
+ protected final int mTextViewResourceId;
+
+ private final int mIconSize;
+ private final View.OnHoverListener mDefaultHoverListener;
+
+ private View.OnHoverListener mHoverListener;
+
+ public AppsAdapter(Context context, int resource, int textViewResourceId) {
+ super(context, resource, textViewResourceId);
+
+ mIconSize = context.getResources().getDimensionPixelSize(R.dimen.app_icon_size);
+ mTextViewResourceId = textViewResourceId;
+ mDefaultHoverListener = new View.OnHoverListener() {
+ @Override
+ public boolean onHover(View v, MotionEvent event) {
+ if (mHoverListener != null) {
+ return mHoverListener.onHover(v, event);
+ } else {
+ return false;
+ }
+ }
+ };
+
+ loadAllApps();
+ }
+
+ public CharSequence getLabel(int position) {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final ResolveInfo appInfo = getItem(position);
+ return appInfo.loadLabel(packageManager);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final View view = super.getView(position, convertView, parent);
+ view.setOnHoverListener(mDefaultHoverListener);
+ view.setTag(position);
+
+ final ResolveInfo appInfo = getItem(position);
+ final CharSequence label = appInfo.loadLabel(packageManager);
+ final Drawable icon = appInfo.loadIcon(packageManager);
+ final TextView text = (TextView) view.findViewById(mTextViewResourceId);
+
+ icon.setBounds(0, 0, mIconSize, mIconSize);
+
+ populateView(text, label, icon);
+
+ return view;
+ }
+
+ private void loadAllApps() {
+ final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+ final PackageManager pm = getContext().getPackageManager();
+ final List<ResolveInfo> apps = pm.queryIntentActivities(mainIntent, 0);
+
+ addAll(apps);
+ }
+
+ protected void populateView(TextView text, CharSequence label, Drawable icon) {
+ text.setText(label);
+ text.setCompoundDrawables(null, icon, null, null);
+ }
+
+ public void setOnHoverListener(View.OnHoverListener hoverListener) {
+ mHoverListener = hoverListener;
+ }
+ }
+
+ /**
+ * Introduces using a finger to explore and interact with on-screen content.
+ */
+ private static class TouchTutorialModule1 extends TutorialModule implements
+ View.OnHoverListener, AdapterView.OnItemClickListener {
+ /**
+ * Handles the case where the user overshoots the target area.
+ */
+ private class HoverTargetHandler extends Handler {
+ private static final int MSG_ENTERED_TARGET = 1;
+ private static final int DELAY_ENTERED_TARGET = 500;
+
+ private boolean mInsideTarget = false;
+
+ public void enteredTarget() {
+ mInsideTarget = true;
+ mHandler.sendEmptyMessageDelayed(MSG_ENTERED_TARGET, DELAY_ENTERED_TARGET);
+ }
+
+ public void exitedTarget() {
+ mInsideTarget = false;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ENTERED_TARGET:
+ if (mInsideTarget) {
+ addInstruction(R.string.accessibility_tutorial_lesson_1_text_4,
+ mTargetName);
+ } else {
+ addInstruction(R.string.accessibility_tutorial_lesson_1_text_4_exited,
+ mTargetName);
+ setFlag(FLAG_TOUCHED_TARGET, false);
+ }
+ break;
+ }
+ }
+ }
+
+ private static final int FLAG_TOUCH_ITEMS = 0x1;
+ private static final int FLAG_TOUCHED_ITEMS = 0x2;
+ private static final int FLAG_TOUCHED_TARGET = 0x4;
+ private static final int FLAG_TAPPED_TARGET = 0x8;
+
+ private static final int MORE_EXPLORED_COUNT = 1;
+ private static final int DONE_EXPLORED_COUNT = 2;
+
+ private final HoverTargetHandler mHandler;
+ private final AppsAdapter mAppsAdapter;
+ private final GridView mAllApps;
+
+ private int mTouched = 0;
+
+ private int mTargetPosition;
+ private CharSequence mTargetName;
+
+ public TouchTutorialModule1(Context context, AccessibilityTutorialActivity controller) {
+ super(context, controller, R.layout.accessibility_tutorial_1,
+ R.string.accessibility_tutorial_lesson_1_title);
+
+ mHandler = new HoverTargetHandler();
+
+ mAppsAdapter = new AppsAdapter(context, R.layout.accessibility_tutorial_app_icon,
+ R.id.app_icon);
+ mAppsAdapter.setOnHoverListener(this);
+
+ mAllApps = (GridView) findViewById(R.id.all_apps);
+ mAllApps.setAdapter(mAppsAdapter);
+ mAllApps.setOnItemClickListener(this);
+
+ findViewById(R.id.next_button).setOnHoverListener(this);
+
+ setSkipVisible(true);
+ }
+
+ @Override
+ public boolean onHover(View v, MotionEvent event) {
+ switch (v.getId()) {
+ case R.id.app_icon:
+ if (hasFlag(FLAG_TOUCH_ITEMS) && !hasFlag(FLAG_TOUCHED_ITEMS) && v.isEnabled()
+ && (event.getAction() == MotionEvent.ACTION_HOVER_ENTER)) {
+ mTouched++;
+
+ if (mTouched >= DONE_EXPLORED_COUNT) {
+ setFlag(FLAG_TOUCHED_ITEMS, true);
+ addInstruction(R.string.accessibility_tutorial_lesson_1_text_3,
+ mTargetName);
+ } else if (mTouched == MORE_EXPLORED_COUNT) {
+ addInstruction(R.string.accessibility_tutorial_lesson_1_text_2_more);
+ }
+
+ v.setEnabled(false);
+ } else if (hasFlag(FLAG_TOUCHED_ITEMS)
+ && ((Integer) v.getTag() == mTargetPosition)) {
+ if (!hasFlag(FLAG_TOUCHED_TARGET)
+ && (event.getAction() == MotionEvent.ACTION_HOVER_ENTER)) {
+ mHandler.enteredTarget();
+ setFlag(FLAG_TOUCHED_TARGET, true);
+ } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+ mHandler.exitedTarget();
+ }
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (hasFlag(FLAG_TOUCHED_TARGET) && !hasFlag(FLAG_TAPPED_TARGET)
+ && (position == mTargetPosition)) {
+ setFlag(FLAG_TAPPED_TARGET, true);
+ final CharSequence nextText = getContext().getText(
+ R.string.accessibility_tutorial_next);
+ addInstruction(R.string.accessibility_tutorial_lesson_1_text_5, nextText);
+ setNextVisible(true);
+ }
+ }
+
+ @Override
+ public void onShown() {
+ final int first = mAllApps.getFirstVisiblePosition();
+ final int last = mAllApps.getLastVisiblePosition();
+
+ mTargetPosition = 0;
+ mTargetName = mAppsAdapter.getLabel(mTargetPosition);
+
+ addInstruction(R.string.accessibility_tutorial_lesson_1_text_1);
+ setFlag(FLAG_TOUCH_ITEMS, true);
+ }
+ }
+
+ /**
+ * Introduces using two fingers to scroll through a list.
+ */
+ private static class TouchTutorialModule2 extends TutorialModule implements
+ AbsListView.OnScrollListener, View.OnHoverListener {
+ private static final int FLAG_EXPLORE_LIST = 0x1;
+ private static final int FLAG_SCROLL_LIST = 0x2;
+ private static final int FLAG_COMPLETED_TUTORIAL = 0x4;
+
+ private static final int MORE_EXPLORE_COUNT = 1;
+ private static final int DONE_EXPLORE_COUNT = 2;
+ private static final int MORE_SCROLL_COUNT = 2;
+ private static final int DONE_SCROLL_COUNT = 4;
+
+ private final AppsAdapter mAppsAdapter;
+
+ private int mExploreCount = 0;
+ private int mInitialVisibleItem = -1;
+ private int mScrollCount = 0;
+
+ public TouchTutorialModule2(Context context, AccessibilityTutorialActivity controller) {
+ super(context, controller, R.layout.accessibility_tutorial_2,
+ R.string.accessibility_tutorial_lesson_2_title);
+
+ mAppsAdapter = new AppsAdapter(context, android.R.layout.simple_list_item_1,
+ android.R.id.text1) {
+ @Override
+ protected void populateView(TextView text, CharSequence label, Drawable icon) {
+ text.setText(label);
+ text.setCompoundDrawables(icon, null, null, null);
+ }
+ };
+ mAppsAdapter.setOnHoverListener(this);
+
+ ((ListView) findViewById(R.id.list_view)).setAdapter(mAppsAdapter);
+ ((ListView) findViewById(R.id.list_view)).setOnScrollListener(this);
+
+ setBackVisible(true);
+ }
+
+ @Override
+ public boolean onHover(View v, MotionEvent e) {
+ if (e.getAction() != MotionEvent.ACTION_HOVER_ENTER) {
+ return false;
+ }
+
+ switch (v.getId()) {
+ case android.R.id.text1:
+ if (hasFlag(FLAG_EXPLORE_LIST) && !hasFlag(FLAG_SCROLL_LIST)) {
+ mExploreCount++;
+
+ if (mExploreCount >= DONE_EXPLORE_COUNT) {
+ addInstruction(R.string.accessibility_tutorial_lesson_2_text_3);
+ setFlag(FLAG_SCROLL_LIST, true);
+ } else if (mExploreCount == MORE_EXPLORE_COUNT) {
+ addInstruction(R.string.accessibility_tutorial_lesson_2_text_2_more);
+ }
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (hasFlag(FLAG_SCROLL_LIST) && !hasFlag(FLAG_COMPLETED_TUTORIAL)) {
+ if (mInitialVisibleItem < 0) {
+ mInitialVisibleItem = firstVisibleItem;
+ }
+
+ final int scrollCount = Math.abs(mInitialVisibleItem - firstVisibleItem);
+
+ if ((mScrollCount == scrollCount) || (scrollCount <= 0)) {
+ return;
+ } else {
+ mScrollCount = scrollCount;
+ }
+
+ if (mScrollCount >= DONE_SCROLL_COUNT) {
+ final CharSequence finishText = getContext().getText(
+ R.string.accessibility_tutorial_finish);
+ addInstruction(R.string.accessibility_tutorial_lesson_2_text_4, finishText);
+ setFlag(FLAG_COMPLETED_TUTORIAL, true);
+ setFinishVisible(true);
+ } else if (mScrollCount == MORE_SCROLL_COUNT) {
+ addInstruction(R.string.accessibility_tutorial_lesson_2_text_3_more);
+ }
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onShown() {
+ addInstruction(R.string.accessibility_tutorial_lesson_2_text_1);
+ setFlag(FLAG_EXPLORE_LIST, true);
+ }
+ }
+
+ /**
+ * Abstract class that represents a single module within a tutorial.
+ */
+ private static abstract class TutorialModule extends FrameLayout implements OnClickListener {
+ private final AccessibilityTutorialActivity mController;
+ private final TextView mInstructions;
+ private final Button mSkip;
+ private final Button mBack;
+ private final Button mNext;
+ private final Button mFinish;
+ private final int mTitleResId;
+
+ /** Which bit flags have been set. */
+ private long mFlags;
+
+ /** Whether this module is currently focused. */
+ private boolean mIsVisible;
+
+ /**
+ * Constructs a new tutorial module for the given context and controller
+ * with the specified layout.
+ *
+ * @param context The parent context.
+ * @param controller The parent tutorial controller.
+ * @param layoutResId The layout to use for this module.
+ */
+ public TutorialModule(Context context, AccessibilityTutorialActivity controller,
+ int layoutResId, int titleResId) {
+ super(context);
+
+ mController = controller;
+ mTitleResId = titleResId;
+
+ final View container = LayoutInflater.from(context).inflate(
+ R.layout.accessibility_tutorial_container, this, true);
+
+ mInstructions = (TextView) container.findViewById(R.id.instructions);
+ mSkip = (Button) container.findViewById(R.id.skip_button);
+ mSkip.setOnClickListener(this);
+ mBack = (Button) container.findViewById(R.id.back_button);
+ mBack.setOnClickListener(this);
+ mNext = (Button) container.findViewById(R.id.next_button);
+ mNext.setOnClickListener(this);
+ mFinish = (Button) container.findViewById(R.id.finish_button);
+ mFinish.setOnClickListener(this);
+
+ final TextView title = (TextView) container.findViewById(R.id.title);
+
+ if (title != null) {
+ title.setText(titleResId);
+ }
+
+ final ViewGroup contentHolder = (ViewGroup) container.findViewById(R.id.content);
+ LayoutInflater.from(context).inflate(layoutResId, contentHolder, true);
+ }
+
+ /**
+ * Called when this tutorial gains focus.
+ */
+ public final void activate() {
+ mIsVisible = true;
+
+ mFlags = 0;
+ mInstructions.setVisibility(View.GONE);
+ mController.setTitle(mTitleResId);
+
+ onShown();
+ }
+
+ /**
+ * Formats an instruction string and adds it to the speaking queue.
+ *
+ * @param resId The resource id of the instruction string.
+ * @param formatArgs Optional formatting arguments.
+ * @see String#format(String, Object...)
+ */
+ protected void addInstruction(final int resId, Object... formatArgs) {
+ if (!mIsVisible) {
+ return;
+ }
+
+ final String text = getContext().getString(resId, formatArgs);
+
+ mInstructions.setVisibility(View.VISIBLE);
+ mInstructions.setText(text);
+ mInstructions.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+
+ /**
+ * Called when this tutorial loses focus.
+ */
+ public void deactivate() {
+ mIsVisible = false;
+
+ mController.interrupt();
+ }
+
+ /**
+ * Returns {@code true} if the flag with the specified id has been set.
+ *
+ * @param flagId The id of the flag to check for.
+ * @return {@code true} if the flag with the specified id has been set.
+ */
+ protected boolean hasFlag(int flagId) {
+ return (mFlags & flagId) == flagId;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.skip_button:
+ mController.finish();
+ break;
+ case R.id.back_button:
+ mController.previous();
+ break;
+ case R.id.next_button:
+ mController.next();
+ break;
+ case R.id.finish_button:
+ mController.finish();
+ break;
+ }
+ }
+
+ public abstract void onShown();
+
+ /**
+ * Sets or removes the flag with the specified id.
+ *
+ * @param flagId The id of the flag to modify.
+ * @param value {@code true} to set the flag, {@code false} to remove
+ * it.
+ */
+ protected void setFlag(int flagId, boolean value) {
+ if (value) {
+ mFlags |= flagId;
+ } else {
+ mFlags = ~(~mFlags | flagId);
+ }
+ }
+
+ protected void setSkipVisible(boolean visible) {
+ mSkip.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ protected void setBackVisible(boolean visible) {
+ mBack.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ protected void setNextVisible(boolean visible) {
+ mNext.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ protected void setFinishVisible(boolean visible) {
+ mFinish.setVisibility(visible ? VISIBLE : GONE);
+ }
+ }
+}
diff --git a/src/com/android/settings/AccountPreference.java b/src/com/android/settings/AccountPreference.java
index f3d7d51..f76d5cb 100644
--- a/src/com/android/settings/AccountPreference.java
+++ b/src/com/android/settings/AccountPreference.java
@@ -71,6 +71,7 @@ public class AccountPreference extends Preference {
setSummary(getSyncStatusMessage(mStatus));
mSyncStatusIcon = (ImageView) view.findViewById(R.id.syncStatusIcon);
mSyncStatusIcon.setImageResource(getSyncStatusIcon(mStatus));
+ mSyncStatusIcon.setContentDescription(getSyncContentDescription(mStatus));
}
public void setProviderIcon(Drawable icon) {
@@ -126,6 +127,20 @@ public class AccountPreference extends Preference {
return res;
}
+ private String getSyncContentDescription(int status) {
+ switch (status) {
+ case SYNC_ENABLED:
+ return getContext().getString(R.string.accessibility_sync_enabled);
+ case SYNC_DISABLED:
+ return getContext().getString(R.string.accessibility_sync_disabled);
+ case SYNC_ERROR:
+ return getContext().getString(R.string.accessibility_sync_error);
+ default:
+ Log.e(TAG, "Unknown sync status: " + status);
+ return getContext().getString(R.string.accessibility_sync_error);
+ }
+ }
+
@Override
public int compareTo(Preference other) {
if (!(other instanceof AccountPreference)) {
diff --git a/src/com/android/settings/ActivityPicker.java b/src/com/android/settings/ActivityPicker.java
index d984adb..ac79cea 100644
--- a/src/com/android/settings/ActivityPicker.java
+++ b/src/com/android/settings/ActivityPicker.java
@@ -399,6 +399,7 @@ public class ActivityPicker extends AlertActivity implements
//noinspection deprecation
icon = new BitmapDrawable(thumb);
((BitmapDrawable) icon).setTargetDensity(mMetrics);
+ canvas.setBitmap(null);
} else if (iconWidth < width && iconHeight < height) {
final Bitmap.Config c = Bitmap.Config.ARGB_8888;
final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
@@ -413,6 +414,7 @@ public class ActivityPicker extends AlertActivity implements
//noinspection deprecation
icon = new BitmapDrawable(thumb);
((BitmapDrawable) icon).setTargetDensity(mMetrics);
+ canvas.setBitmap(null);
}
}
diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java
index 00c416f..94ba5a1 100644
--- a/src/com/android/settings/AirplaneModeEnabler.java
+++ b/src/com/android/settings/AirplaneModeEnabler.java
@@ -16,8 +16,6 @@
package com.android.settings;
-import com.android.internal.telephony.PhoneStateIntentReceiver;
-
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
@@ -27,8 +25,8 @@ import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.provider.Settings;
-import android.telephony.ServiceState;
+import com.android.internal.telephony.PhoneStateIntentReceiver;
import com.android.internal.telephony.TelephonyProperties;
public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListener {
@@ -93,10 +91,6 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
}
private void setAirplaneModeOn(boolean enabling) {
-
- mCheckBoxPref.setSummary(enabling ? R.string.airplane_mode_turning_on
- : R.string.airplane_mode_turning_off);
-
// Change the system setting
Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
enabling ? 1 : 0);
@@ -118,10 +112,7 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
* - mobile does not send failure notification, fail on timeout.
*/
private void onAirplaneModeChanged() {
- boolean airplaneModeEnabled = isAirplaneModeOn(mContext);
mCheckBoxPref.setChecked(isAirplaneModeOn(mContext));
- mCheckBoxPref.setSummary(airplaneModeEnabled ? null :
- mContext.getString(R.string.airplane_mode_summary));
}
/**
diff --git a/src/com/android/settings/ApplicationSettings.java b/src/com/android/settings/ApplicationSettings.java
index da417ec..27fc3ec 100644
--- a/src/com/android/settings/ApplicationSettings.java
+++ b/src/com/android/settings/ApplicationSettings.java
@@ -16,21 +16,18 @@
package com.android.settings;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.res.Configuration;
+import android.content.Intent;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
-import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceScreen;
import android.provider.Settings;
-public class ApplicationSettings extends SettingsPreferenceFragment implements
- DialogInterface.OnClickListener {
+public class ApplicationSettings extends SettingsPreferenceFragment {
- private static final String KEY_TOGGLE_INSTALL_APPLICATIONS = "toggle_install_applications";
+ private static final String KEY_TOGGLE_ADVANCED_SETTINGS = "toggle_advanced_settings";
private static final String KEY_APP_INSTALL_LOCATION = "app_install_location";
// App installation location. Default is ask the user.
@@ -42,20 +39,24 @@ public class ApplicationSettings extends SettingsPreferenceFragment implements
private static final String APP_INSTALL_SDCARD_ID = "sdcard";
private static final String APP_INSTALL_AUTO_ID = "auto";
- private CheckBoxPreference mToggleAppInstallation;
-
+ private CheckBoxPreference mToggleAdvancedSettings;
private ListPreference mInstallLocation;
- private DialogInterface mWarnInstallApps;
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.application_settings);
- mToggleAppInstallation = (CheckBoxPreference) findPreference(KEY_TOGGLE_INSTALL_APPLICATIONS);
- mToggleAppInstallation.setChecked(isNonMarketAppsAllowed());
+ mToggleAdvancedSettings = (CheckBoxPreference)findPreference(
+ KEY_TOGGLE_ADVANCED_SETTINGS);
+ mToggleAdvancedSettings.setChecked(isAdvancedSettingsEnabled());
+ getPreferenceScreen().removePreference(mToggleAdvancedSettings);
+
+ // not ready for prime time yet
+ if (false) {
+ getPreferenceScreen().removePreference(mInstallLocation);
+ }
mInstallLocation = (ListPreference) findPreference(KEY_APP_INSTALL_LOCATION);
// Is app default install location set?
@@ -94,43 +95,29 @@ public class ApplicationSettings extends SettingsPreferenceFragment implements
}
@Override
- public void onDestroy() {
- super.onDestroy();
- if (mWarnInstallApps != null) {
- mWarnInstallApps.dismiss();
- }
- }
-
- @Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (preference == mToggleAppInstallation) {
- if (mToggleAppInstallation.isChecked()) {
- mToggleAppInstallation.setChecked(false);
- warnAppInstallation();
- } else {
- setNonMarketAppsAllowed(false);
- }
+ if (preference == mToggleAdvancedSettings) {
+ boolean value = mToggleAdvancedSettings.isChecked();
+ setAdvancedSettingsEnabled(value);
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
- public void onClick(DialogInterface dialog, int which) {
- if (dialog == mWarnInstallApps && which == DialogInterface.BUTTON_POSITIVE) {
- setNonMarketAppsAllowed(true);
- mToggleAppInstallation.setChecked(true);
- }
+ private boolean isAdvancedSettingsEnabled() {
+ return Settings.System.getInt(getContentResolver(),
+ Settings.System.ADVANCED_SETTINGS,
+ Settings.System.ADVANCED_SETTINGS_DEFAULT) > 0;
}
- private void setNonMarketAppsAllowed(boolean enabled) {
+ private void setAdvancedSettingsEnabled(boolean enabled) {
+ int value = enabled ? 1 : 0;
// Change the system setting
- Settings.Secure.putInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS,
- enabled ? 1 : 0);
- }
-
- private boolean isNonMarketAppsAllowed() {
- return Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
+ Settings.Secure.putInt(getContentResolver(), Settings.System.ADVANCED_SETTINGS, value);
+ // TODO: the settings thing should broadcast this for thread safety purposes.
+ Intent intent = new Intent(Intent.ACTION_ADVANCED_SETTINGS_CHANGED);
+ intent.putExtra("state", value);
+ getActivity().sendBroadcast(intent);
}
private String getAppInstallLocation() {
@@ -147,15 +134,4 @@ public class ApplicationSettings extends SettingsPreferenceFragment implements
return APP_INSTALL_AUTO_ID;
}
}
-
- private void warnAppInstallation() {
- // TODO: DialogFragment?
- mWarnInstallApps = new AlertDialog.Builder(getActivity()).setTitle(
- getResources().getString(R.string.error_title))
- .setIcon(com.android.internal.R.drawable.ic_dialog_alert)
- .setMessage(getResources().getString(R.string.install_all_warning))
- .setPositiveButton(android.R.string.yes, this)
- .setNegativeButton(android.R.string.no, null)
- .show();
- }
}
diff --git a/src/com/android/settings/BatteryInfo.java b/src/com/android/settings/BatteryInfo.java
index 2f9d50e..d8046cf 100644
--- a/src/com/android/settings/BatteryInfo.java
+++ b/src/com/android/settings/BatteryInfo.java
@@ -26,7 +26,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.Message;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.text.format.DateUtils;
@@ -91,26 +90,7 @@ public class BatteryInfo extends Activity {
+ getString(R.string.battery_info_temperature_units));
mTechnology.setText("" + intent.getStringExtra("technology"));
- int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
- String statusString;
- if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
- statusString = getString(R.string.battery_info_status_charging);
- if (plugType > 0) {
- statusString = statusString + " " + getString(
- (plugType == BatteryManager.BATTERY_PLUGGED_AC)
- ? R.string.battery_info_status_charging_ac
- : R.string.battery_info_status_charging_usb);
- }
- } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
- statusString = getString(R.string.battery_info_status_discharging);
- } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
- statusString = getString(R.string.battery_info_status_not_charging);
- } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
- statusString = getString(R.string.battery_info_status_full);
- } else {
- statusString = getString(R.string.battery_info_status_unknown);
- }
- mStatus.setText(statusString);
+ mStatus.setText(Utils.getBatteryStatus(getResources(), intent));
switch (plugType) {
case 0:
@@ -198,7 +178,6 @@ public class BatteryInfo extends Activity {
private void updateBatteryStats() {
long uptime = SystemClock.elapsedRealtime();
mUptime.setText(DateUtils.formatElapsedTime(uptime / 1000));
-
}
}
diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java
index 9bbb66a..df50ada 100644
--- a/src/com/android/settings/BrightnessPreference.java
+++ b/src/com/android/settings/BrightnessPreference.java
@@ -26,7 +26,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.preference.SeekBarPreference;
+import android.preference.SeekBarDialogPreference;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.AttributeSet;
@@ -35,7 +35,7 @@ import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.SeekBar;
-public class BrightnessPreference extends SeekBarPreference implements
+public class BrightnessPreference extends SeekBarDialogPreference implements
SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener {
private SeekBar mSeekBar;
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
index 118bc6f..8311c4a 100644
--- a/src/com/android/settings/ChooseLockGeneric.java
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -27,6 +27,7 @@ import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
+import android.security.KeyStore;
public class ChooseLockGeneric extends PreferenceActivity {
@@ -48,9 +49,11 @@ public class ChooseLockGeneric extends PreferenceActivity {
private static final int CONFIRM_EXISTING_REQUEST = 100;
private static final String PASSWORD_CONFIRMED = "password_confirmed";
private static final String CONFIRM_CREDENTIALS = "confirm_credentials";
+ public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private DevicePolicyManager mDPM;
+ private KeyStore mKeyStore;
private boolean mPasswordConfirmed = false;
@Override
@@ -58,6 +61,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
super.onCreate(savedInstanceState);
mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mKeyStore = KeyStore.getInstance();
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
if (savedInstanceState != null) {
@@ -126,8 +130,8 @@ public class ChooseLockGeneric extends PreferenceActivity {
.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);
if (quality == -1) {
// If caller didn't specify password quality, show UI and allow the user to choose.
- quality = mDPM.getPasswordQuality(null);
- quality = upgradeQualityForEncryption(quality);
+ quality = getActivity().getIntent().getIntExtra(MINIMUM_QUALITY_KEY, -1);
+ quality = upgradeQuality(quality);
final PreferenceScreen prefScreen = getPreferenceScreen();
if (prefScreen != null) {
prefScreen.removeAll();
@@ -135,11 +139,26 @@ public class ChooseLockGeneric extends PreferenceActivity {
addPreferencesFromResource(R.xml.security_settings_picker);
disableUnusablePreferences(quality);
} else {
- quality = upgradeQualityForEncryption(quality);
updateUnlockMethodAndFinish(quality, false);
}
}
+ private int upgradeQuality(int quality) {
+ quality = upgradeQualityForDPM(quality);
+ quality = upgradeQualityForEncryption(quality);
+ quality = upgradeQualityForKeyStore(quality);
+ return quality;
+ }
+
+ private int upgradeQualityForDPM(int quality) {
+ // Compare min allowed password quality
+ int minQuality = mDPM.getPasswordQuality(null);
+ if (quality < minQuality) {
+ quality = minQuality;
+ }
+ return quality;
+ }
+
/**
* Mix in "encryption minimums" to any given quality value. This prevents users
* from downgrading the pattern/pin/password to a level below the minimums.
@@ -152,8 +171,17 @@ public class ChooseLockGeneric extends PreferenceActivity {
boolean encrypted = (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE)
|| (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING);
if (encrypted) {
- if (quality < DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ if (quality < CryptKeeperSettings.MIN_PASSWORD_QUALITY) {
+ quality = CryptKeeperSettings.MIN_PASSWORD_QUALITY;
+ }
+ }
+ return quality;
+ }
+
+ private int upgradeQualityForKeyStore(int quality) {
+ if (!mKeyStore.isEmpty()) {
+ if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
+ quality = CredentialStorage.MIN_PASSWORD_QUALITY;
}
}
return quality;
@@ -208,13 +236,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
throw new IllegalStateException("Tried to update password without confirming it");
}
- // Compare min allowed password quality and launch appropriate security setting method
- int minQuality = mDPM.getPasswordQuality(null);
- if (quality < minQuality) {
- quality = minQuality;
- }
- quality = upgradeQualityForEncryption(quality);
-
+ quality = upgradeQuality(quality);
if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
int minLength = mDPM.getPasswordMinimumLength(null);
if (minLength < MIN_PASSWORD_LENGTH) {
diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java
index a0f2346..96255eb 100644
--- a/src/com/android/settings/ChooseLockPassword.java
+++ b/src/com/android/settings/ChooseLockPassword.java
@@ -405,8 +405,10 @@ public class ChooseLockPassword extends PreferenceActivity {
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) {
+ // Check if this was the result of hitting the enter or "done" key
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
handleNext();
return true;
}
diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java
index 55f6254..9a34f2f 100644
--- a/src/com/android/settings/ChooseLockPattern.java
+++ b/src/com/android/settings/ChooseLockPattern.java
@@ -235,7 +235,7 @@ public class ChooseLockPattern extends PreferenceActivity {
Introduction(
R.string.lockpattern_recording_intro_header,
LeftButtonMode.Cancel, RightButtonMode.ContinueDisabled,
- R.string.lockpattern_recording_intro_footer, true),
+ ID_EMPTY_MESSAGE, true),
HelpScreen(
R.string.lockpattern_settings_help_how_to_record,
LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java
index d31fe3b..a069712 100644
--- a/src/com/android/settings/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/ChooseLockSettingsHelper.java
@@ -23,7 +23,10 @@ import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
-public class ChooseLockSettingsHelper {
+public final class ChooseLockSettingsHelper {
+
+ static final String EXTRA_KEY_PASSWORD = "password";
+
private LockPatternUtils mLockPatternUtils;
private Activity mActivity;
private Fragment mFragment;
@@ -49,8 +52,7 @@ public class ChooseLockSettingsHelper {
* @return true if one exists and we launched an activity to confirm it
* @see #onActivityResult(int, int, android.content.Intent)
*/
- protected boolean launchConfirmationActivity(int request,
- CharSequence message, CharSequence details) {
+ boolean launchConfirmationActivity(int request, CharSequence message, CharSequence details) {
boolean launched = false;
switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java
index 3f4a4f3..1229046 100644
--- a/src/com/android/settings/ConfirmLockPassword.java
+++ b/src/com/android/settings/ConfirmLockPassword.java
@@ -27,13 +27,16 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceActivity;
+import android.text.Editable;
import android.text.InputType;
+import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -58,7 +61,7 @@ public class ConfirmLockPassword extends PreferenceActivity {
}
public static class ConfirmLockPasswordFragment extends Fragment implements OnClickListener,
- OnEditorActionListener {
+ OnEditorActionListener, TextWatcher {
private static final long ERROR_MESSAGE_TIMEOUT = 3000;
private TextView mPasswordEntry;
private LockPatternUtils mLockPatternUtils;
@@ -66,6 +69,7 @@ public class ConfirmLockPassword extends PreferenceActivity {
private Handler mHandler = new Handler();
private PasswordEntryKeyboardHelper mKeyboardHelper;
private PasswordEntryKeyboardView mKeyboardView;
+ private Button mContinueButton;
// required constructor for fragments
@@ -87,9 +91,14 @@ public class ConfirmLockPassword extends PreferenceActivity {
// Disable IME on our window since we provide our own keyboard
view.findViewById(R.id.cancel_button).setOnClickListener(this);
- view.findViewById(R.id.next_button).setOnClickListener(this);
+ mContinueButton = (Button) view.findViewById(R.id.next_button);
+ mContinueButton.setOnClickListener(this);
+ mContinueButton.setEnabled(false); // disable until the user enters at least one char
+
mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
mPasswordEntry.setOnEditorActionListener(this);
+ mPasswordEntry.addTextChangedListener(this);
+
mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
mHeaderText = (TextView) view.findViewById(R.id.headerText);
final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
@@ -140,7 +149,7 @@ public class ConfirmLockPassword extends PreferenceActivity {
if (mLockPatternUtils.checkPassword(pin)) {
Intent intent = new Intent();
- intent.putExtra("password", pin);
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
getActivity().setResult(RESULT_OK, intent);
getActivity().finish();
@@ -172,13 +181,27 @@ public class ConfirmLockPassword extends PreferenceActivity {
}, ERROR_MESSAGE_TIMEOUT);
}
+ // {@link OnEditorActionListener} methods.
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- if (actionId == EditorInfo.IME_NULL) {
+ // Check if this was the result of hitting the enter or "done" key
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
handleNext();
return true;
}
return false;
}
+
+ // {@link TextWatcher} methods.
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ public void afterTextChanged(Editable s) {
+ mContinueButton.setEnabled(mPasswordEntry.getText().length() > 0);
+ }
}
}
diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
index 0653d3f..2892930 100644
--- a/src/com/android/settings/ConfirmLockPattern.java
+++ b/src/com/android/settings/ConfirmLockPattern.java
@@ -256,7 +256,12 @@ public class ConfirmLockPattern extends PreferenceActivity {
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
if (mLockPatternUtils.checkPattern(pattern)) {
- getActivity().setResult(Activity.RESULT_OK);
+
+ Intent intent = new Intent();
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
+ LockPatternUtils.patternToString(pattern));
+
+ getActivity().setResult(Activity.RESULT_OK, intent);
getActivity().finish();
} else {
if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL &&
diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java
index 9d5a603..e246fce 100644
--- a/src/com/android/settings/CredentialStorage.java
+++ b/src/com/android/settings/CredentialStorage.java
@@ -18,213 +18,417 @@ package com.android.settings;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.res.Resources;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.security.KeyChain.KeyChainConnection;
+import android.security.KeyChain;
import android.security.KeyStore;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
-import java.io.UnsupportedEncodingException;
+/**
+ * CredentialStorage handles KeyStore reset, unlock, and install.
+ *
+ * CredentialStorage has a pretty convoluted state machine to migrate
+ * from the old style separate keystore password to a new key guard
+ * based password, as well as to deal with setting up the key guard if
+ * necessary.
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: factory state
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: ON
+ * Action: confirm key guard
+ * Notes: user had key guard but no keystore and upgraded from pre-ICS
+ * OR user had key guard and pre-ICS keystore password which was then reset
+ *
+ * KeyStore: LOCKED
+ * KeyGuard: OFF/ON
+ * Action: old unlock dialog
+ * Notes: assume old password, need to use it to unlock.
+ * if unlock, ensure key guard before install.
+ * if reset, treat as UNINITALIZED/OFF
+ *
+ * KeyStore: UNLOCKED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: ensure key guard, then proceed
+ *
+ * KeyStore: UNLOCKED
+ * keyguard: ON
+ * Action: normal unlock/install
+ * Notes: this is the common case
+ */
+public final class CredentialStorage extends Activity {
-public class CredentialStorage extends Activity implements TextWatcher,
- DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+ private static final String TAG = "CredentialStorage";
public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
- public static final String ACTION_SET_PASSWORD = "com.android.credentials.SET_PASSWORD";
public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
public static final String ACTION_RESET = "com.android.credentials.RESET";
- private static final String TAG = "CredentialStorage";
+ // This is the minimum acceptable password quality. If the current password quality is
+ // lower than this, keystore should not be activated.
+ static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- private KeyStore mKeyStore = KeyStore.getInstance();
- private boolean mSubmit = false;
- private Bundle mBundle;
+ private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
- private TextView mOldPassword;
- private TextView mNewPassword;
- private TextView mConfirmPassword;
- private TextView mError;
- private Button mButton;
+ private final KeyStore mKeyStore = KeyStore.getInstance();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ /**
+ * When non-null, the bundle containing credentials to install.
+ */
+ private Bundle mInstallBundle;
+
+ /**
+ * After unsuccessful KeyStore.unlock, the number of unlock
+ * attempts remaining before the KeyStore will reset itself.
+ *
+ * Reset to -1 on successful unlock or reset.
+ */
+ private int mRetriesRemaining = -1;
+
+ @Override protected void onResume() {
+ super.onResume();
Intent intent = getIntent();
String action = intent.getAction();
- int state = mKeyStore.test();
if (ACTION_RESET.equals(action)) {
- showResetDialog();
- } else if (ACTION_SET_PASSWORD.equals(action)) {
- showPasswordDialog(state == KeyStore.UNINITIALIZED);
+ new ResetDialog();
} else {
if (ACTION_INSTALL.equals(action) &&
"com.android.certinstaller".equals(getCallingPackage())) {
- mBundle = intent.getExtras();
- }
- if (state == KeyStore.UNINITIALIZED) {
- showPasswordDialog(true);
- } else if (state == KeyStore.LOCKED) {
- showUnlockDialog();
- } else {
- install();
- finish();
+ mInstallBundle = intent.getExtras();
}
+ // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
+ handleUnlockOrInstall();
}
}
- private void install() {
- if (mBundle != null && !mBundle.isEmpty()) {
- try {
- for (String key : mBundle.keySet()) {
- byte[] value = mBundle.getByteArray(key);
- if (value != null && !mKeyStore.put(key.getBytes("UTF-8"), value)) {
- Log.e(TAG, "Failed to install " + key);
- return;
- }
+ /**
+ * Based on the current state of the KeyStore and key guard, try to
+ * make progress on unlocking or installing to the keystore.
+ */
+ private void handleUnlockOrInstall() {
+ // something already decided we are done, do not proceed
+ if (isFinishing()) {
+ return;
+ }
+ switch (mKeyStore.state()) {
+ case UNINITIALIZED: {
+ ensureKeyGuard();
+ return;
+ }
+ case LOCKED: {
+ new UnlockDialog();
+ return;
+ }
+ case UNLOCKED: {
+ if (!checkKeyGuardQuality()) {
+ new ConfigureKeyGuardDialog();
+ return;
}
- setResult(RESULT_OK);
- } catch (UnsupportedEncodingException e) {
- // Should never happen.
- throw new RuntimeException(e);
+ installIfAvailable();
+ finish();
+ return;
}
}
}
- private void showResetDialog() {
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.credentials_reset_hint)
- .setNeutralButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
+ /**
+ * Make sure the user enters the key guard to set or change the
+ * keystore password. This can be used in UNINITIALIZED to set the
+ * keystore password or UNLOCKED to change the password (as is the
+ * case after unlocking with an old-style password).
+ */
+ private void ensureKeyGuard() {
+ if (!checkKeyGuardQuality()) {
+ // key guard not setup, doing so will initialize keystore
+ new ConfigureKeyGuardDialog();
+ // will return to onResume after Activity
+ return;
+ }
+ // force key guard confirmation
+ if (confirmKeyGuard()) {
+ // will return password value via onActivityResult
+ return;
+ }
+ finish();
}
- private void showPasswordDialog(boolean firstTime) {
- View view = View.inflate(this, R.layout.credentials_dialog, null);
+ /**
+ * Returns true if the currently set key guard matches our minimum quality requirements.
+ */
+ private boolean checkKeyGuardQuality() {
+ int quality = new LockPatternUtils(this).getActivePasswordQuality();
+ return (quality >= MIN_PASSWORD_QUALITY);
+ }
- ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_password_hint);
- if (!firstTime) {
- view.findViewById(R.id.old_password_prompt).setVisibility(View.VISIBLE);
- mOldPassword = (TextView) view.findViewById(R.id.old_password);
- mOldPassword.setVisibility(View.VISIBLE);
- mOldPassword.addTextChangedListener(this);
+ /**
+ * Install credentials if available, otherwise do nothing.
+ */
+ private void installIfAvailable() {
+ if (mInstallBundle != null && !mInstallBundle.isEmpty()) {
+ Bundle bundle = mInstallBundle;
+ mInstallBundle = null;
+ for (String key : bundle.keySet()) {
+ byte[] value = bundle.getByteArray(key);
+ if (value != null && !mKeyStore.put(key, value)) {
+ Log.e(TAG, "Failed to install " + key);
+ return;
+ }
+ }
+ setResult(RESULT_OK);
}
- view.findViewById(R.id.new_passwords).setVisibility(View.VISIBLE);
- mNewPassword = (TextView) view.findViewById(R.id.new_password);
- mNewPassword.addTextChangedListener(this);
- mConfirmPassword = (TextView) view.findViewById(R.id.confirm_password);
- mConfirmPassword.addTextChangedListener(this);
- mError = (TextView) view.findViewById(R.id.error);
-
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setView(view)
- .setTitle(R.string.credentials_set_password)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
- mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- mButton.setEnabled(false);
}
- private void showUnlockDialog() {
- View view = View.inflate(this, R.layout.credentials_dialog, null);
-
- ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_unlock_hint);
- mOldPassword = (TextView) view.findViewById(R.id.old_password);
- mOldPassword.setVisibility(View.VISIBLE);
- mOldPassword.addTextChangedListener(this);
- mError = (TextView) view.findViewById(R.id.error);
-
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setView(view)
- .setTitle(R.string.credentials_unlock)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
- mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- mButton.setEnabled(false);
- }
+ /**
+ * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
+ */
+ private class ResetDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mResetConfirmed;
- public void afterTextChanged(Editable editable) {
- if ((mOldPassword == null || mOldPassword.getText().length() > 0) &&
- (mNewPassword == null || mNewPassword.getText().length() >= 8) &&
- (mConfirmPassword == null || mConfirmPassword.getText().length() >= 8)) {
- mButton.setEnabled(true);
- } else {
- mButton.setEnabled(false);
+ private ResetDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.credentials_reset_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
}
- }
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
- public void onTextChanged(CharSequence s,int start, int before, int count) {
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mResetConfirmed) {
+ mResetConfirmed = false;
+ new ResetKeyStoreAndKeyChain().execute();
+ return;
+ }
+ finish();
+ }
}
- public void onClick(DialogInterface dialog, int button) {
- mSubmit = (button == DialogInterface.BUTTON_POSITIVE);
- if (button == DialogInterface.BUTTON_NEUTRAL) {
+ /**
+ * Background task to handle reset of both keystore and user installed CAs.
+ */
+ private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
+
+ @Override protected Boolean doInBackground(Void... unused) {
+
mKeyStore.reset();
- Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show();
+
+ try {
+ KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
+ try {
+ return keyChainConnection.getService().reset();
+ } catch (RemoteException e) {
+ return false;
+ } finally {
+ keyChainConnection.close();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ @Override protected void onPostExecute(Boolean success) {
+ if (success) {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
+ }
+ finish();
}
}
- public void onDismiss(DialogInterface dialog) {
- if (mSubmit) {
- mSubmit = false;
- mError.setVisibility(View.VISIBLE);
+ /**
+ * Prompt for key guard configuration confirmation.
+ */
+ private class ConfigureKeyGuardDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mConfigureConfirmed;
- if (mNewPassword == null) {
- mKeyStore.unlock(mOldPassword.getText().toString());
- } else {
- String newPassword = mNewPassword.getText().toString();
- String confirmPassword = mConfirmPassword.getText().toString();
+ private ConfigureKeyGuardDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.credentials_configure_lock_screen_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
- if (!newPassword.equals(confirmPassword)) {
- mError.setText(R.string.credentials_passwords_mismatch);
- ((AlertDialog) dialog).show();
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mConfigureConfirmed) {
+ mConfigureConfirmed = false;
+ Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
+ MIN_PASSWORD_QUALITY);
+ startActivity(intent);
+ return;
+ }
+ finish();
+ }
+ }
+
+ /**
+ * Confirm existing key guard, returning password via onActivityResult.
+ */
+ private boolean confirmKeyGuard() {
+ Resources res = getResources();
+ boolean launched = new ChooseLockSettingsHelper(this)
+ .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
+ res.getText(R.string.master_clear_gesture_prompt),
+ res.getText(R.string.master_clear_gesture_explanation));
+ return launched;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ /**
+ * Receive key guard password initiated by confirmKeyGuard.
+ */
+ if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+ if (!TextUtils.isEmpty(password)) {
+ // success
+ mKeyStore.password(password);
+ // return to onResume
return;
- } else if (mOldPassword == null) {
- mKeyStore.password(newPassword);
- } else {
- mKeyStore.password(mOldPassword.getText().toString(), newPassword);
}
}
+ // failed confirmation, bail
+ finish();
+ }
+ }
+
+ /**
+ * Prompt for unlock with old-style password.
+ *
+ * On successful unlock, ensure migration to key guard before continuing.
+ * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
+ */
+ private class UnlockDialog implements TextWatcher,
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mUnlockConfirmed;
+
+ private final Button mButton;
+ private final TextView mOldPassword;
+ private final TextView mError;
+
+ private UnlockDialog() {
+ View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
+
+ CharSequence text;
+ if (mRetriesRemaining == -1) {
+ text = getResources().getText(R.string.credentials_unlock_hint);
+ } else if (mRetriesRemaining > 3) {
+ text = getResources().getText(R.string.credentials_wrong_password);
+ } else if (mRetriesRemaining == 1) {
+ text = getResources().getText(R.string.credentials_reset_warning);
+ } else {
+ text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
+ }
- int error = mKeyStore.getLastError();
- if (error == KeyStore.NO_ERROR) {
- Toast.makeText(this, R.string.credentials_enabled, Toast.LENGTH_SHORT).show();
- install();
- } else if (error == KeyStore.UNINITIALIZED) {
- Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show();
- } else if (error >= KeyStore.WRONG_PASSWORD) {
- int count = error - KeyStore.WRONG_PASSWORD + 1;
- if (count > 3) {
- mError.setText(R.string.credentials_wrong_password);
- } else if (count == 1) {
- mError.setText(R.string.credentials_reset_warning);
- } else {
- mError.setText(getString(R.string.credentials_reset_warning_plural, count));
+ ((TextView) view.findViewById(R.id.hint)).setText(text);
+ mOldPassword = (TextView) view.findViewById(R.id.old_password);
+ mOldPassword.setVisibility(View.VISIBLE);
+ mOldPassword.addTextChangedListener(this);
+ mError = (TextView) view.findViewById(R.id.error);
+
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setView(view)
+ .setTitle(R.string.credentials_unlock)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ mButton.setEnabled(false);
+ }
+
+ @Override public void afterTextChanged(Editable editable) {
+ mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
+ }
+
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
+
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mUnlockConfirmed) {
+ mUnlockConfirmed = false;
+ mError.setVisibility(View.VISIBLE);
+ mKeyStore.unlock(mOldPassword.getText().toString());
+ int error = mKeyStore.getLastError();
+ if (error == KeyStore.NO_ERROR) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_enabled,
+ Toast.LENGTH_SHORT).show();
+ // aha, now we are unlocked, switch to key guard.
+ // we'll end up back in onResume to install
+ ensureKeyGuard();
+ } else if (error == KeyStore.UNINITIALIZED) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased,
+ Toast.LENGTH_SHORT).show();
+ // we are reset, we can now set new password with key guard
+ handleUnlockOrInstall();
+ } else if (error >= KeyStore.WRONG_PASSWORD) {
+ // we need to try again
+ mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
+ handleUnlockOrInstall();
}
- ((AlertDialog) dialog).show();
return;
}
+ finish();
}
- finish();
}
}
diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java
index edf00d5..612f4c0 100644
--- a/src/com/android/settings/CryptKeeper.java
+++ b/src/com/android/settings/CryptKeeper.java
@@ -16,6 +16,7 @@
package com.android.settings;
+import com.android.internal.telephony.ITelephony;
import com.android.internal.widget.PasswordEntryKeyboardHelper;
import com.android.internal.widget.PasswordEntryKeyboardView;
@@ -33,9 +34,11 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.storage.IMountService;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -62,6 +65,9 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
private static final int COOL_DOWN_ATTEMPTS = 10;
private static final int COOL_DOWN_INTERVAL = 30; // 30 seconds
+ // Intent action for launching the Emergency Dialer activity.
+ static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+
private int mCooldown;
PowerManager.WakeLock mWakeLock;
private EditText mPasswordEntry;
@@ -342,9 +348,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
- PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this,
- keyboardView, mPasswordEntry, false);
- keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
+ if (keyboardView != null) {
+ PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this,
+ keyboardView, mPasswordEntry, false);
+ keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
+ }
+
+ updateEmergencyCallButtonState();
}
private IMountService getMountService() {
@@ -357,7 +367,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (actionId == EditorInfo.IME_NULL) {
+ if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE) {
// Get the password
String password = v.getText().toString();
@@ -379,4 +389,73 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList
}
return false;
}
-} \ No newline at end of file
+
+ //
+ // Code to update the state of, and handle clicks from, the "Emergency call" button.
+ //
+ // This code is mostly duplicated from the corresponding code in
+ // LockPatternUtils and LockPatternKeyguardView under frameworks/base.
+ //
+
+ private void updateEmergencyCallButtonState() {
+ Button button = (Button) findViewById(R.id.emergencyCallButton);
+ // The button isn't present at all in some configurations.
+ if (button == null) return;
+
+ if (isEmergencyCallCapable()) {
+ button.setVisibility(View.VISIBLE);
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ takeEmergencyCallAction();
+ }
+ });
+ } else {
+ button.setVisibility(View.GONE);
+ return;
+ }
+
+ int newState = TelephonyManager.getDefault().getCallState();
+ int textId;
+ if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
+ // show "return to call" text and show phone icon
+ textId = R.string.cryptkeeper_return_to_call;
+ int phoneCallIcon = R.drawable.stat_sys_phone_call;
+ button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
+ } else {
+ textId = R.string.cryptkeeper_emergency_call;
+ int emergencyIcon = R.drawable.ic_emergency;
+ button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
+ }
+ button.setText(textId);
+ }
+
+ private boolean isEmergencyCallCapable() {
+ return getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
+ }
+
+ private void takeEmergencyCallAction() {
+ if (TelephonyManager.getDefault().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK) {
+ resumeCall();
+ } else {
+ launchEmergencyDialer();
+ }
+ }
+
+ private void resumeCall() {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ if (phone != null) {
+ try {
+ phone.showCallScreen();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony service: " + e);
+ }
+ }
+ }
+
+ private void launchEmergencyDialer() {
+ Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ startActivity(intent);
+ }
+}
diff --git a/src/com/android/settings/CryptKeeperConfirm.java b/src/com/android/settings/CryptKeeperConfirm.java
index ba8ce10..d177cb4 100644
--- a/src/com/android/settings/CryptKeeperConfirm.java
+++ b/src/com/android/settings/CryptKeeperConfirm.java
@@ -62,6 +62,8 @@ public class CryptKeeperConfirm extends Fragment {
public void run() {
IBinder service = ServiceManager.getService("mount");
if (service == null) {
+ Log.e("CryptKeeper", "Failed to find the mount service");
+ finish();
return;
}
diff --git a/src/com/android/settings/CryptKeeperSettings.java b/src/com/android/settings/CryptKeeperSettings.java
index 10fa8ac..41a4be5 100644
--- a/src/com/android/settings/CryptKeeperSettings.java
+++ b/src/com/android/settings/CryptKeeperSettings.java
@@ -37,16 +37,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-/**
- * Confirm and execute a reset of the device to a clean "just out of the box"
- * state. Multiple confirmations are required: first, a general "are you sure
- * you want to do this?" prompt, followed by a keyguard pattern trace if the user
- * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
- * ON THE PHONE" prompt. If at any time the phone is allowed to go to sleep, is
- * locked, et cetera, then the confirmation sequence is abandoned.
- *
- * This is the initial screen.
- */
public class CryptKeeperSettings extends Fragment {
private static final String TAG = "CryptKeeper";
@@ -54,7 +44,7 @@ public class CryptKeeperSettings extends Fragment {
// This is the minimum acceptable password quality. If the current password quality is
// lower than this, encryption should not be activated.
- private static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
// Minimum battery charge level (in percent) to launch encryption. If the battery charge is
// lower than this, encryption should not be activated.
@@ -73,8 +63,14 @@ public class CryptKeeperSettings extends Fragment {
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ int invalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
+
boolean levelOk = level >= MIN_BATTERY_LEVEL;
- boolean pluggedOk = plugged == BatteryManager.BATTERY_PLUGGED_AC;
+ boolean pluggedOk =
+ (plugged == BatteryManager.BATTERY_PLUGGED_AC ||
+ plugged == BatteryManager.BATTERY_PLUGGED_USB) &&
+ invalidCharger == 0;
+
// Update UI elements based on power/battery status
mInitiateButton.setEnabled(levelOk && pluggedOk);
mPowerWarning.setVisibility(pluggedOk ? View.GONE : View.VISIBLE );
@@ -163,7 +159,7 @@ public class CryptKeeperSettings extends Fragment {
*/
private boolean runKeyguardConfirmation(int request) {
// 1. Confirm that we have a sufficient PIN/Password to continue
- int quality = new LockPatternUtils(getActivity()).getKeyguardStoredPasswordQuality();
+ int quality = new LockPatternUtils(getActivity()).getActivePasswordQuality();
if (quality < MIN_PASSWORD_QUALITY) {
return false;
}
@@ -186,7 +182,7 @@ public class CryptKeeperSettings extends Fragment {
// If the user entered a valid keyguard trace, present the final
// confirmation prompt; otherwise, go back to the initial state.
if (resultCode == Activity.RESULT_OK && data != null) {
- String password = data.getStringExtra("password");
+ String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
if (!TextUtils.isEmpty(password)) {
showFinalConfirmation(password);
}
diff --git a/src/com/android/settings/DataUsageSummary.java b/src/com/android/settings/DataUsageSummary.java
new file mode 100644
index 0000000..bccc5a5
--- /dev/null
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -0,0 +1,1713 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
+import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.net.NetworkTemplate.buildTemplateEthernet;
+import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
+import static android.net.NetworkTemplate.buildTemplateMobile4g;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateWifi;
+import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
+import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
+import static android.text.format.Time.TIMEZONE_UTC;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.animation.LayoutTransition;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Formatter;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.NumberPicker;
+import android.widget.ProgressBar;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TabHost;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabContentFactory;
+import android.widget.TabHost.TabSpec;
+import android.widget.TabWidget;
+import android.widget.TextView;
+
+import com.android.internal.telephony.Phone;
+import com.android.settings.net.NetworkPolicyEditor;
+import com.android.settings.net.SummaryForAllUidLoader;
+import com.android.settings.widget.DataUsageChartView;
+import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Locale;
+
+import libcore.util.Objects;
+
+/**
+ * Panel show data usage history across various networks, including options to
+ * inspect based on usage cycle and control through {@link NetworkPolicy}.
+ */
+public class DataUsageSummary extends Fragment {
+ private static final String TAG = "DataUsage";
+ private static final boolean LOGD = true;
+
+ // TODO: remove this testing code
+ private static final boolean TEST_ANIM = false;
+ private static final boolean TEST_RADIOS = false;
+ private static final String TEST_RADIOS_PROP = "test.radios";
+
+ private static final String TAB_3G = "3g";
+ private static final String TAB_4G = "4g";
+ private static final String TAB_MOBILE = "mobile";
+ private static final String TAB_WIFI = "wifi";
+ private static final String TAB_ETHERNET = "ethernet";
+
+ private static final String TAG_CONFIRM_ROAMING = "confirmRoaming";
+ private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
+ private static final String TAG_CYCLE_EDITOR = "cycleEditor";
+ private static final String TAG_CONFIRM_RESTRICT = "confirmRestrict";
+ private static final String TAG_CONFIRM_APP_RESTRICT = "confirmAppRestrict";
+ private static final String TAG_APP_DETAILS = "appDetails";
+
+ private static final int LOADER_SUMMARY = 2;
+
+ private static final long KB_IN_BYTES = 1024;
+ private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+ private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+
+ private INetworkManagementService mNetworkService;
+ private INetworkStatsService mStatsService;
+ private INetworkPolicyManager mPolicyService;
+ private ConnectivityManager mConnService;
+
+ private static final String PREF_FILE = "data_usage";
+ private static final String PREF_SHOW_WIFI = "show_wifi";
+ private static final String PREF_SHOW_ETHERNET = "show_ethernet";
+
+ private SharedPreferences mPrefs;
+
+ private TabHost mTabHost;
+ private ViewGroup mTabsContainer;
+ private TabWidget mTabWidget;
+ private ListView mListView;
+ private DataUsageAdapter mAdapter;
+
+ private ViewGroup mHeader;
+
+ private ViewGroup mNetworkSwitchesContainer;
+ private LinearLayout mNetworkSwitches;
+ private Switch mDataEnabled;
+ private View mDataEnabledView;
+ private CheckBox mDisableAtLimit;
+ private View mDisableAtLimitView;
+
+ private View mCycleView;
+ private Spinner mCycleSpinner;
+ private CycleAdapter mCycleAdapter;
+
+ private DataUsageChartView mChart;
+ private TextView mUsageSummary;
+ private TextView mEmpty;
+
+ private View mAppDetail;
+ private ImageView mAppIcon;
+ private ViewGroup mAppTitles;
+ private Button mAppSettings;
+
+ private LinearLayout mAppSwitches;
+ private CheckBox mAppRestrict;
+ private View mAppRestrictView;
+
+ private boolean mShowWifi = false;
+ private boolean mShowEthernet = false;
+
+ private NetworkTemplate mTemplate = null;
+
+ private static final int UID_NONE = -1;
+ private int mUid = UID_NONE;
+
+ private Intent mAppSettingsIntent;
+
+ private NetworkPolicyEditor mPolicyEditor;
+
+ private NetworkStatsHistory mHistory;
+ private NetworkStatsHistory mDetailHistory;
+
+ private String mCurrentTab = null;
+ private String mIntentTab = null;
+
+ private MenuItem mMenuDataRoaming;
+ private MenuItem mMenuRestrictBackground;
+
+ /** Flag used to ignore listeners during binding. */
+ private boolean mBinding;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mNetworkService = INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+ mStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ mPolicyService = INetworkPolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ mConnService = (ConnectivityManager) getActivity().getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+
+ mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
+
+ mPolicyEditor = new NetworkPolicyEditor(mPolicyService);
+ mPolicyEditor.read();
+
+ mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
+ mShowEthernet = mPrefs.getBoolean(PREF_SHOW_ETHERNET, false);
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ final Context context = inflater.getContext();
+ final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
+
+ mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
+ mTabsContainer = (ViewGroup) view.findViewById(R.id.tabs_container);
+ mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
+ mListView = (ListView) view.findViewById(android.R.id.list);
+
+ mTabHost.setup();
+ mTabHost.setOnTabChangedListener(mTabListener);
+
+ mHeader = (ViewGroup) inflater.inflate(R.layout.data_usage_header, mListView, false);
+ mListView.addHeaderView(mHeader, null, false);
+
+ {
+ // bind network switches
+ mNetworkSwitchesContainer = (ViewGroup) mHeader.findViewById(
+ R.id.network_switches_container);
+ mNetworkSwitches = (LinearLayout) mHeader.findViewById(R.id.network_switches);
+
+ mDataEnabled = new Switch(inflater.getContext());
+ mDataEnabledView = inflatePreference(inflater, mNetworkSwitches, mDataEnabled);
+ mDataEnabled.setOnCheckedChangeListener(mDataEnabledListener);
+ mNetworkSwitches.addView(mDataEnabledView);
+
+ mDisableAtLimit = new CheckBox(inflater.getContext());
+ mDisableAtLimit.setClickable(false);
+ mDisableAtLimitView = inflatePreference(inflater, mNetworkSwitches, mDisableAtLimit);
+ mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
+ mNetworkSwitches.addView(mDisableAtLimitView);
+ }
+
+ // bind cycle dropdown
+ mCycleView = mHeader.findViewById(R.id.cycles);
+ mCycleSpinner = (Spinner) mCycleView.findViewById(R.id.cycles_spinner);
+ mCycleAdapter = new CycleAdapter(context);
+ mCycleSpinner.setAdapter(mCycleAdapter);
+ mCycleSpinner.setOnItemSelectedListener(mCycleListener);
+
+ mChart = (DataUsageChartView) mHeader.findViewById(R.id.chart);
+ mChart.setListener(mChartListener);
+
+ {
+ // bind app detail controls
+ mAppDetail = mHeader.findViewById(R.id.app_detail);
+ mAppIcon = (ImageView) mAppDetail.findViewById(R.id.app_icon);
+ mAppTitles = (ViewGroup) mAppDetail.findViewById(R.id.app_titles);
+ mAppSwitches = (LinearLayout) mAppDetail.findViewById(R.id.app_switches);
+
+ mAppSettings = (Button) mAppDetail.findViewById(R.id.app_settings);
+ mAppSettings.setOnClickListener(mAppSettingsListener);
+
+ mAppRestrict = new CheckBox(inflater.getContext());
+ mAppRestrict.setClickable(false);
+ mAppRestrictView = inflatePreference(inflater, mAppSwitches, mAppRestrict);
+ setPreferenceTitle(mAppRestrictView, R.string.data_usage_app_restrict_background);
+ setPreferenceSummary(
+ mAppRestrictView, R.string.data_usage_app_restrict_background_summary);
+ mAppRestrictView.setOnClickListener(mAppRestrictListener);
+ mAppSwitches.addView(mAppRestrictView);
+ }
+
+ mUsageSummary = (TextView) mHeader.findViewById(R.id.usage_summary);
+ mEmpty = (TextView) mHeader.findViewById(android.R.id.empty);
+
+ // only assign layout transitions once first layout is finished
+ mListView.getViewTreeObserver().addOnGlobalLayoutListener(mFirstLayoutListener);
+
+ mAdapter = new DataUsageAdapter();
+ mListView.setOnItemClickListener(mListListener);
+ mListView.setAdapter(mAdapter);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // pick default tab based on incoming intent
+ final Intent intent = getActivity().getIntent();
+ mIntentTab = computeTabFromIntent(intent);
+
+ // this kicks off chain reaction which creates tabs, binds the body to
+ // selected network, and binds chart, cycles and detail list.
+ updateTabs();
+
+ // kick off background task to update stats
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ mStatsService.forceUpdate();
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (isAdded()) {
+ updateBody();
+ }
+ }
+ }.execute();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.data_usage, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ final Context context = getActivity();
+
+ mMenuDataRoaming = menu.findItem(R.id.data_usage_menu_roaming);
+ mMenuDataRoaming.setVisible(hasMobileRadio(context));
+ mMenuDataRoaming.setChecked(getDataRoaming());
+
+ mMenuRestrictBackground = menu.findItem(R.id.data_usage_menu_restrict_background);
+ mMenuRestrictBackground.setChecked(getRestrictBackground());
+
+ final MenuItem split4g = menu.findItem(R.id.data_usage_menu_split_4g);
+ split4g.setVisible(hasMobile4gRadio(context));
+ split4g.setChecked(isMobilePolicySplit());
+
+ final MenuItem showWifi = menu.findItem(R.id.data_usage_menu_show_wifi);
+ if (hasWifiRadio(context) && hasMobileRadio(context)) {
+ showWifi.setVisible(true);
+ showWifi.setChecked(mShowWifi);
+ } else {
+ showWifi.setVisible(false);
+ mShowWifi = true;
+ }
+
+ final MenuItem showEthernet = menu.findItem(R.id.data_usage_menu_show_ethernet);
+ if (hasEthernet(context) && hasMobileRadio(context)) {
+ showEthernet.setVisible(true);
+ showEthernet.setChecked(mShowEthernet);
+ } else {
+ showEthernet.setVisible(false);
+ mShowEthernet = true;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.data_usage_menu_roaming: {
+ final boolean dataRoaming = !item.isChecked();
+ if (dataRoaming) {
+ ConfirmDataRoamingFragment.show(this);
+ } else {
+ // no confirmation to disable roaming
+ setDataRoaming(false);
+ }
+ return true;
+ }
+ case R.id.data_usage_menu_restrict_background: {
+ final boolean restrictBackground = !item.isChecked();
+ if (restrictBackground) {
+ ConfirmRestrictFragment.show(this);
+ } else {
+ // no confirmation to drop restriction
+ setRestrictBackground(false);
+ }
+ return true;
+ }
+ case R.id.data_usage_menu_split_4g: {
+ final boolean mobileSplit = !item.isChecked();
+ setMobilePolicySplit(mobileSplit);
+ item.setChecked(isMobilePolicySplit());
+ updateTabs();
+ return true;
+ }
+ case R.id.data_usage_menu_show_wifi: {
+ mShowWifi = !item.isChecked();
+ mPrefs.edit().putBoolean(PREF_SHOW_WIFI, mShowWifi).apply();
+ item.setChecked(mShowWifi);
+ updateTabs();
+ return true;
+ }
+ case R.id.data_usage_menu_show_ethernet: {
+ mShowEthernet = !item.isChecked();
+ mPrefs.edit().putBoolean(PREF_SHOW_ETHERNET, mShowEthernet).apply();
+ item.setChecked(mShowEthernet);
+ updateTabs();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ mDataEnabledView = null;
+ mDisableAtLimitView = null;
+ }
+
+ /**
+ * Listener to setup {@link LayoutTransition} after first layout pass.
+ */
+ private OnGlobalLayoutListener mFirstLayoutListener = new OnGlobalLayoutListener() {
+ /** {@inheritDoc} */
+ public void onGlobalLayout() {
+ mListView.getViewTreeObserver().removeGlobalOnLayoutListener(mFirstLayoutListener);
+
+ mTabsContainer.setLayoutTransition(buildLayoutTransition());
+ mHeader.setLayoutTransition(buildLayoutTransition());
+ mNetworkSwitchesContainer.setLayoutTransition(buildLayoutTransition());
+
+ final LayoutTransition chartTransition = buildLayoutTransition();
+ chartTransition.setStartDelay(LayoutTransition.APPEARING, 0);
+ chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
+ mChart.setLayoutTransition(chartTransition);
+ }
+ };
+
+ private static LayoutTransition buildLayoutTransition() {
+ final LayoutTransition transition = new LayoutTransition();
+ if (TEST_ANIM) {
+ transition.setDuration(1500);
+ }
+ transition.setAnimateParentHierarchy(false);
+ return transition;
+ }
+
+ /**
+ * Rebuild all tabs based on {@link NetworkPolicyEditor} and
+ * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
+ * first tab, and kicks off a full rebind of body contents.
+ */
+ private void updateTabs() {
+ final Context context = getActivity();
+ mTabHost.clearAllTabs();
+
+ final boolean mobileSplit = isMobilePolicySplit();
+ if (mobileSplit && hasMobile4gRadio(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
+ mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
+ } else if (hasMobileRadio(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
+ }
+ if (mShowWifi && hasWifiRadio(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
+ }
+ if (mShowEthernet && hasEthernet(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_ETHERNET, R.string.data_usage_tab_ethernet));
+ }
+
+ final boolean multipleTabs = mTabWidget.getTabCount() > 1;
+ mTabWidget.setVisibility(multipleTabs ? View.VISIBLE : View.GONE);
+ if (mIntentTab != null) {
+ if (Objects.equal(mIntentTab, mTabHost.getCurrentTabTag())) {
+ updateBody();
+ } else {
+ mTabHost.setCurrentTabByTag(mIntentTab);
+ }
+ mIntentTab = null;
+ } else {
+ if (mTabHost.getCurrentTab() == 0) {
+ updateBody();
+ } else {
+ mTabHost.setCurrentTab(0);
+ }
+ }
+ }
+
+ /**
+ * Factory that provide empty {@link View} to make {@link TabHost} happy.
+ */
+ private TabContentFactory mEmptyTabContent = new TabContentFactory() {
+ /** {@inheritDoc} */
+ public View createTabContent(String tag) {
+ return new View(mTabHost.getContext());
+ }
+ };
+
+ /**
+ * Build {@link TabSpec} with thin indicator, and empty content.
+ */
+ private TabSpec buildTabSpec(String tag, int titleRes) {
+ final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
+ final View indicator = inflater.inflate(
+ R.layout.tab_indicator_thin_holo, mTabWidget, false);
+ final TextView title = (TextView) indicator.findViewById(android.R.id.title);
+ title.setText(titleRes);
+ return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
+ }
+
+ private OnTabChangeListener mTabListener = new OnTabChangeListener() {
+ /** {@inheritDoc} */
+ public void onTabChanged(String tabId) {
+ // user changed tab; update body
+ updateBody();
+ }
+ };
+
+ /**
+ * Update body content based on current tab. Loads
+ * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
+ * binds them to visible controls.
+ */
+ private void updateBody() {
+ mBinding = true;
+
+ final Context context = getActivity();
+ final String currentTab = mTabHost.getCurrentTabTag();
+
+ if (currentTab == null) {
+ Log.w(TAG, "no tab selected; hiding body");
+ mListView.setVisibility(View.GONE);
+ return;
+ } else {
+ mListView.setVisibility(View.VISIBLE);
+ }
+
+ final boolean tabChanged = !currentTab.equals(mCurrentTab);
+ mCurrentTab = currentTab;
+
+ if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
+
+ if (TAB_WIFI.equals(currentTab)) {
+ // wifi doesn't have any controls
+ mDataEnabledView.setVisibility(View.GONE);
+ mDisableAtLimitView.setVisibility(View.GONE);
+ mTemplate = buildTemplateWifi();
+
+ } else if (TAB_ETHERNET.equals(currentTab)) {
+ // ethernet doesn't have any controls
+ mDataEnabledView.setVisibility(View.GONE);
+ mDisableAtLimitView.setVisibility(View.GONE);
+ mTemplate = buildTemplateEthernet();
+
+ } else {
+ mDataEnabledView.setVisibility(View.VISIBLE);
+ mDisableAtLimitView.setVisibility(View.VISIBLE);
+ }
+
+ if (TAB_MOBILE.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
+ mDataEnabled.setChecked(mConnService.getMobileDataEnabled());
+ mTemplate = buildTemplateMobileAll(getActiveSubscriberId(context));
+
+ } else if (TAB_3G.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
+ // TODO: bind mDataEnabled to 3G radio state
+ mTemplate = buildTemplateMobile3gLower(getActiveSubscriberId(context));
+
+ } else if (TAB_4G.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
+ // TODO: bind mDataEnabled to 4G radio state
+ mTemplate = buildTemplateMobile4g(getActiveSubscriberId(context));
+ }
+
+ try {
+ // load stats for current template
+ mHistory = mStatsService.getHistoryForNetwork(
+ mTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES);
+ } catch (RemoteException e) {
+ // since we can't do much without policy or history, and we don't
+ // want to leave with half-baked UI, we bail hard.
+ throw new RuntimeException("problem reading network policy or stats", e);
+ }
+
+ // bind chart to historical stats
+ mChart.bindNetworkStats(mHistory);
+
+ // only update policy when switching tabs
+ updatePolicy(tabChanged);
+ updateAppDetail();
+
+ // force scroll to top of body
+ mListView.smoothScrollToPosition(0);
+
+ mBinding = false;
+ }
+
+ private boolean isAppDetailMode() {
+ return mUid != UID_NONE;
+ }
+
+ /**
+ * Update UID details panels to match {@link #mUid}, showing or hiding them
+ * depending on {@link #isAppDetailMode()}.
+ */
+ private void updateAppDetail() {
+ final Context context = getActivity();
+ final PackageManager pm = context.getPackageManager();
+ final LayoutInflater inflater = getActivity().getLayoutInflater();
+
+ if (isAppDetailMode()) {
+ mAppDetail.setVisibility(View.VISIBLE);
+ mCycleAdapter.setChangeVisible(false);
+ } else {
+ mAppDetail.setVisibility(View.GONE);
+ mCycleAdapter.setChangeVisible(true);
+
+ // hide detail stats when not in detail mode
+ mChart.bindDetailNetworkStats(null);
+ return;
+ }
+
+ // remove warning/limit sweeps while in detail mode
+ mChart.bindNetworkPolicy(null);
+
+ // show icon and all labels appearing under this app
+ final UidDetail detail = resolveDetailForUid(context, mUid);
+ mAppIcon.setImageDrawable(detail.icon);
+
+ mAppTitles.removeAllViews();
+ if (detail.detailLabels != null) {
+ for (CharSequence label : detail.detailLabels) {
+ mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, label));
+ }
+ } else {
+ mAppTitles.addView(inflateAppTitle(inflater, mAppTitles, detail.label));
+ }
+
+ // enable settings button when package provides it
+ // TODO: target torwards entire UID instead of just first package
+ final String[] packageNames = pm.getPackagesForUid(mUid);
+ if (packageNames != null && packageNames.length > 0) {
+ mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
+ mAppSettingsIntent.setPackage(packageNames[0]);
+ mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
+
+ final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
+ mAppSettings.setEnabled(matchFound);
+
+ } else {
+ mAppSettingsIntent = null;
+ mAppSettings.setEnabled(false);
+ }
+
+ try {
+ // load stats for current uid and template
+ // TODO: read template from extras
+ mDetailHistory = mStatsService.getHistoryForUid(
+ mTemplate, mUid, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+ } catch (RemoteException e) {
+ // since we can't do much without history, and we don't want to
+ // leave with half-baked UI, we bail hard.
+ throw new RuntimeException("problem reading network stats", e);
+ }
+
+ // bind chart to historical stats
+ mChart.bindDetailNetworkStats(mDetailHistory);
+
+ updateDetailData();
+
+ if (NetworkPolicyManager.isUidValidForPolicy(context, mUid) && !getRestrictBackground()
+ && isBandwidthControlEnabled()) {
+ mAppRestrictView.setVisibility(View.VISIBLE);
+ mAppRestrict.setChecked(getAppRestrictBackground());
+
+ } else {
+ mAppRestrictView.setVisibility(View.GONE);
+ }
+
+ }
+
+ private void setPolicyCycleDay(int cycleDay) {
+ if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
+ mPolicyEditor.setPolicyCycleDay(mTemplate, cycleDay);
+ updatePolicy(true);
+ }
+
+ private void setPolicyWarningBytes(long warningBytes) {
+ if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
+ mPolicyEditor.setPolicyWarningBytes(mTemplate, warningBytes);
+ updatePolicy(false);
+ }
+
+ private void setPolicyLimitBytes(long limitBytes) {
+ if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
+ mPolicyEditor.setPolicyLimitBytes(mTemplate, limitBytes);
+ updatePolicy(false);
+ }
+
+ private boolean isNetworkPolicyModifiable() {
+ return isBandwidthControlEnabled() && mDataEnabled.isChecked();
+ }
+
+ private boolean isBandwidthControlEnabled() {
+ try {
+ return mNetworkService.isBandwidthControlEnabled();
+ } catch (RemoteException e) {
+ Log.w(TAG, "problem talking with INetworkManagementService: " + e);
+ return false;
+ }
+ }
+
+ private boolean getDataRoaming() {
+ final ContentResolver resolver = getActivity().getContentResolver();
+ return Settings.Secure.getInt(resolver, Settings.Secure.DATA_ROAMING, 0) != 0;
+ }
+
+ private void setDataRoaming(boolean enabled) {
+ // TODO: teach telephony DataConnectionTracker to watch and apply
+ // updates when changed.
+ final ContentResolver resolver = getActivity().getContentResolver();
+ Settings.Secure.putInt(resolver, Settings.Secure.DATA_ROAMING, enabled ? 1 : 0);
+ mMenuDataRoaming.setChecked(enabled);
+ }
+
+ private boolean getRestrictBackground() {
+ try {
+ return mPolicyService.getRestrictBackground();
+ } catch (RemoteException e) {
+ Log.w(TAG, "problem talking with policy service: " + e);
+ return false;
+ }
+ }
+
+ private void setRestrictBackground(boolean restrictBackground) {
+ if (LOGD) Log.d(TAG, "setRestrictBackground()");
+ try {
+ mPolicyService.setRestrictBackground(restrictBackground);
+ mMenuRestrictBackground.setChecked(restrictBackground);
+ } catch (RemoteException e) {
+ Log.w(TAG, "problem talking with policy service: " + e);
+ }
+ }
+
+ private boolean getAppRestrictBackground() {
+ final int uidPolicy;
+ try {
+ uidPolicy = mPolicyService.getUidPolicy(mUid);
+ } catch (RemoteException e) {
+ // since we can't do much without policy, we bail hard.
+ throw new RuntimeException("problem reading network policy", e);
+ }
+
+ return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+ }
+
+ private void setAppRestrictBackground(boolean restrictBackground) {
+ if (LOGD) Log.d(TAG, "setAppRestrictBackground()");
+ try {
+ mPolicyService.setUidPolicy(
+ mUid, restrictBackground ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE);
+ } catch (RemoteException e) {
+ throw new RuntimeException("unable to save policy", e);
+ }
+
+ mAppRestrict.setChecked(restrictBackground);
+ }
+
+ /**
+ * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
+ * current {@link #mTemplate}.
+ */
+ private void updatePolicy(boolean refreshCycle) {
+ if (isAppDetailMode()) {
+ mNetworkSwitches.setVisibility(View.GONE);
+ // we fall through to update cycle list for detail mode
+ } else {
+ mNetworkSwitches.setVisibility(View.VISIBLE);
+
+ // when heading back to summary without cycle refresh, kick details
+ // update to repopulate list.
+ if (!refreshCycle) {
+ updateDetailData();
+ }
+ }
+
+ final NetworkPolicy policy = mPolicyEditor.getPolicy(mTemplate);
+ if (isNetworkPolicyModifiable()) {
+ mDisableAtLimitView.setVisibility(View.VISIBLE);
+ mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
+ if (!isAppDetailMode()) {
+ mChart.bindNetworkPolicy(policy);
+ }
+
+ } else {
+ // controls are disabled; don't bind warning/limit sweeps
+ mDisableAtLimitView.setVisibility(View.GONE);
+ mChart.bindNetworkPolicy(null);
+ }
+
+ if (refreshCycle) {
+ // generate cycle list based on policy and available history
+ updateCycleList(policy);
+ }
+ }
+
+ /**
+ * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
+ * and available {@link NetworkStatsHistory} data. Always selects the newest
+ * item, updating the inspection range on {@link #mChart}.
+ */
+ private void updateCycleList(NetworkPolicy policy) {
+ mCycleAdapter.clear();
+
+ final Context context = mCycleSpinner.getContext();
+ long historyStart = mHistory.getStart();
+ long historyEnd = mHistory.getEnd();
+
+ if (historyStart == Long.MAX_VALUE || historyEnd == Long.MIN_VALUE) {
+ historyStart = System.currentTimeMillis();
+ historyEnd = System.currentTimeMillis();
+ }
+
+ boolean hasCycles = false;
+ if (policy != null) {
+ // find the next cycle boundary
+ long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
+
+ int guardCount = 0;
+
+ // walk backwards, generating all valid cycle ranges
+ while (cycleEnd > historyStart) {
+ final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
+ Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
+ + historyStart);
+ mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
+ cycleEnd = cycleStart;
+ hasCycles = true;
+
+ // TODO: remove this guard once we have better testing
+ if (guardCount++ > 50) {
+ Log.wtf(TAG, "stuck generating ranges for historyStart=" + historyStart
+ + ", historyEnd=" + historyEnd + " and policy=" + policy);
+ }
+ }
+
+ // one last cycle entry to modify policy cycle day
+ mCycleAdapter.setChangePossible(isNetworkPolicyModifiable());
+ }
+
+ if (!hasCycles) {
+ // no valid cycles; show all data
+ // TODO: offer simple ranges like "last week" etc
+ mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
+ mCycleAdapter.setChangePossible(false);
+ }
+
+ // force pick the current cycle (first item)
+ mCycleSpinner.setSelection(0);
+ mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
+ }
+
+ private OnCheckedChangeListener mDataEnabledListener = new OnCheckedChangeListener() {
+ /** {@inheritDoc} */
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (mBinding) return;
+
+ final boolean dataEnabled = isChecked;
+ mDataEnabled.setChecked(dataEnabled);
+
+ final String currentTab = mCurrentTab;
+ if (TAB_MOBILE.equals(currentTab)) {
+ mConnService.setMobileDataEnabled(dataEnabled);
+ }
+
+ // rebind policy to match radio state
+ updatePolicy(true);
+ }
+ };
+
+ private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ final boolean disableAtLimit = !mDisableAtLimit.isChecked();
+ if (disableAtLimit) {
+ // enabling limit; show confirmation dialog which eventually
+ // calls setPolicyLimitBytes() once user confirms.
+ ConfirmLimitFragment.show(DataUsageSummary.this);
+ } else {
+ setPolicyLimitBytes(LIMIT_DISABLED);
+ }
+ }
+ };
+
+ private View.OnClickListener mAppRestrictListener = new View.OnClickListener() {
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ final boolean restrictBackground = !mAppRestrict.isChecked();
+
+ if (restrictBackground) {
+ // enabling restriction; show confirmation dialog which
+ // eventually calls setRestrictBackground() once user confirms.
+ ConfirmAppRestrictFragment.show(DataUsageSummary.this);
+ } else {
+ setAppRestrictBackground(false);
+ }
+ }
+ };
+
+ private OnClickListener mAppSettingsListener = new OnClickListener() {
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ // TODO: target torwards entire UID instead of just first package
+ startActivity(mAppSettingsIntent);
+ }
+ };
+
+ private OnItemClickListener mListListener = new OnItemClickListener() {
+ /** {@inheritDoc} */
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
+ AppDetailsFragment.show(DataUsageSummary.this, app.uid);
+ }
+ };
+
+ private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
+ /** {@inheritDoc} */
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
+ if (cycle instanceof CycleChangeItem) {
+ // show cycle editor; will eventually call setPolicyCycleDay()
+ // when user finishes editing.
+ CycleEditorFragment.show(DataUsageSummary.this);
+
+ // reset spinner to something other than "change cycle..."
+ mCycleSpinner.setSelection(0);
+
+ } else {
+ if (LOGD) {
+ Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
+ + cycle.end + "]");
+ }
+
+ // update chart to show selected cycle, and update detail data
+ // to match updated sweep bounds.
+ mChart.setVisibleRange(cycle.start, cycle.end);
+
+ updateDetailData();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void onNothingSelected(AdapterView<?> parent) {
+ // ignored
+ }
+ };
+
+ /**
+ * Update details based on {@link #mChart} inspection range depending on
+ * current mode. In network mode, updates {@link #mAdapter} with sorted list
+ * of applications data usage, and when {@link #isAppDetailMode()} update
+ * app details.
+ */
+ private void updateDetailData() {
+ if (LOGD) Log.d(TAG, "updateDetailData()");
+
+ final long start = mChart.getInspectStart();
+ final long end = mChart.getInspectEnd();
+ final long now = System.currentTimeMillis();
+
+ final Context context = getActivity();
+ final NetworkStatsHistory.Entry entry;
+
+ if (isAppDetailMode()) {
+ if (mDetailHistory != null) {
+ entry = mDetailHistory.getValues(start, end, now, null);
+ } else {
+ entry = null;
+ }
+
+ getLoaderManager().destroyLoader(LOADER_SUMMARY);
+
+ } else {
+ entry = mHistory.getValues(start, end, now, null);
+
+ // kick off loader for detailed stats
+ getLoaderManager().restartLoader(LOADER_SUMMARY,
+ SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
+ }
+
+ final long totalBytes = entry != null ? entry.rxBytes + entry.txBytes : 0;
+ final String totalPhrase = Formatter.formatFileSize(context, totalBytes);
+ final String rangePhrase = formatDateRange(context, start, end, false);
+
+ mUsageSummary.setText(
+ getString(R.string.data_usage_total_during_range, totalPhrase, rangePhrase));
+ }
+
+ private final LoaderCallbacks<NetworkStats> mSummaryForAllUid = new LoaderCallbacks<
+ NetworkStats>() {
+ /** {@inheritDoc} */
+ public Loader<NetworkStats> onCreateLoader(int id, Bundle args) {
+ return new SummaryForAllUidLoader(getActivity(), mStatsService, args);
+ }
+
+ /** {@inheritDoc} */
+ public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) {
+ mAdapter.bindStats(data);
+ updateEmptyVisible();
+ }
+
+ /** {@inheritDoc} */
+ public void onLoaderReset(Loader<NetworkStats> loader) {
+ mAdapter.bindStats(null);
+ updateEmptyVisible();
+ }
+
+ private void updateEmptyVisible() {
+ final boolean isEmpty = mAdapter.isEmpty() && !isAppDetailMode();
+ mEmpty.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
+ }
+ };
+
+ private boolean isMobilePolicySplit() {
+ final Context context = getActivity();
+ if (hasMobileRadio(context)) {
+ final String subscriberId = getActiveSubscriberId(context);
+ return mPolicyEditor.isMobilePolicySplit(subscriberId);
+ } else {
+ return false;
+ }
+ }
+
+ private void setMobilePolicySplit(boolean split) {
+ final String subscriberId = getActiveSubscriberId(getActivity());
+ mPolicyEditor.setMobilePolicySplit(subscriberId, split);
+ }
+
+ private static String getActiveSubscriberId(Context context) {
+ final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ return telephony.getSubscriberId();
+ }
+
+ private DataUsageChartListener mChartListener = new DataUsageChartListener() {
+ /** {@inheritDoc} */
+ public void onInspectRangeChanged() {
+ if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
+ updateDetailData();
+ }
+
+ /** {@inheritDoc} */
+ public void onWarningChanged() {
+ setPolicyWarningBytes(mChart.getWarningBytes());
+ }
+
+ /** {@inheritDoc} */
+ public void onLimitChanged() {
+ setPolicyLimitBytes(mChart.getLimitBytes());
+ }
+ };
+
+
+ /**
+ * List item that reflects a specific data usage cycle.
+ */
+ public static class CycleItem {
+ public CharSequence label;
+ public long start;
+ public long end;
+
+ CycleItem(CharSequence label) {
+ this.label = label;
+ }
+
+ public CycleItem(Context context, long start, long end) {
+ this.label = formatDateRange(context, start, end, true);
+ this.start = start;
+ this.end = end;
+ }
+
+ @Override
+ public String toString() {
+ return label.toString();
+ }
+ }
+
+ private static final StringBuilder sBuilder = new StringBuilder(50);
+ private static final java.util.Formatter sFormatter = new java.util.Formatter(
+ sBuilder, Locale.getDefault());
+
+ public static String formatDateRange(Context context, long start, long end, boolean utcTime) {
+ final int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+ final String timezone = utcTime ? TIMEZONE_UTC : null;
+
+ synchronized (sBuilder) {
+ sBuilder.setLength(0);
+ return DateUtils
+ .formatDateRange(context, sFormatter, start, end, flags, timezone).toString();
+ }
+ }
+
+ /**
+ * Special-case data usage cycle that triggers dialog to change
+ * {@link NetworkPolicy#cycleDay}.
+ */
+ public static class CycleChangeItem extends CycleItem {
+ public CycleChangeItem(Context context) {
+ super(context.getString(R.string.data_usage_change_cycle));
+ }
+ }
+
+ public static class CycleAdapter extends ArrayAdapter<CycleItem> {
+ private boolean mChangePossible = false;
+ private boolean mChangeVisible = false;
+
+ private final CycleChangeItem mChangeItem;
+
+ public CycleAdapter(Context context) {
+ super(context, android.R.layout.simple_spinner_item);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mChangeItem = new CycleChangeItem(context);
+ }
+
+ public void setChangePossible(boolean possible) {
+ mChangePossible = possible;
+ updateChange();
+ }
+
+ public void setChangeVisible(boolean visible) {
+ mChangeVisible = visible;
+ updateChange();
+ }
+
+ private void updateChange() {
+ remove(mChangeItem);
+ if (mChangePossible && mChangeVisible) {
+ add(mChangeItem);
+ }
+ }
+ }
+
+ private static class AppUsageItem implements Comparable<AppUsageItem> {
+ public int uid;
+ public long total;
+
+ /** {@inheritDoc} */
+ public int compareTo(AppUsageItem another) {
+ return Long.compare(another.total, total);
+ }
+ }
+
+ /**
+ * Adapter of applications, sorted by total usage descending.
+ */
+ public static class DataUsageAdapter extends BaseAdapter {
+ private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
+ private long mLargest;
+
+ /**
+ * Bind the given {@link NetworkStats}, or {@code null} to clear list.
+ */
+ public void bindStats(NetworkStats stats) {
+ mItems.clear();
+
+ if (stats != null) {
+ final AppUsageItem systemItem = new AppUsageItem();
+ systemItem.uid = android.os.Process.SYSTEM_UID;
+
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < stats.size(); i++) {
+ entry = stats.getValues(i, entry);
+
+ final boolean isApp = entry.uid >= android.os.Process.FIRST_APPLICATION_UID
+ && entry.uid <= android.os.Process.LAST_APPLICATION_UID;
+ if (isApp || entry.uid == TrafficStats.UID_REMOVED) {
+ final AppUsageItem item = new AppUsageItem();
+ item.uid = entry.uid;
+ item.total = entry.rxBytes + entry.txBytes;
+ mItems.add(item);
+ } else {
+ systemItem.total += entry.rxBytes + entry.txBytes;
+ }
+ }
+
+ if (systemItem.total > 0) {
+ mItems.add(systemItem);
+ }
+ }
+
+ Collections.sort(mItems);
+ mLargest = (mItems.size() > 0) ? mItems.get(0).total : 0;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mItems.get(position).uid;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.data_usage_item, parent, false);
+ }
+
+ final Context context = parent.getContext();
+
+ final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
+ final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+ final ProgressBar progress = (ProgressBar) convertView.findViewById(
+ android.R.id.progress);
+
+ final AppUsageItem item = mItems.get(position);
+ final UidDetail detail = resolveDetailForUid(context, item.uid);
+
+ icon.setImageDrawable(detail.icon);
+ title.setText(detail.label);
+ summary.setText(Formatter.formatFileSize(context, item.total));
+
+ final int percentTotal = mLargest != 0 ? (int) (item.total * 100 / mLargest) : 0;
+ progress.setProgress(percentTotal);
+
+ return convertView;
+ }
+
+ }
+
+ /**
+ * Empty {@link Fragment} that controls display of UID details in
+ * {@link DataUsageSummary}.
+ */
+ public static class AppDetailsFragment extends Fragment {
+ private static final String EXTRA_UID = "uid";
+
+ public static void show(DataUsageSummary parent, int uid) {
+ final Bundle args = new Bundle();
+ args.putInt(EXTRA_UID, uid);
+
+ final AppDetailsFragment fragment = new AppDetailsFragment();
+ fragment.setArguments(args);
+ fragment.setTargetFragment(parent, 0);
+
+ final FragmentTransaction ft = parent.getFragmentManager().beginTransaction();
+ ft.add(fragment, TAG_APP_DETAILS);
+ ft.addToBackStack(TAG_APP_DETAILS);
+ ft.commit();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ target.mUid = getArguments().getInt(EXTRA_UID);
+ target.updateBody();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ target.mUid = UID_NONE;
+ target.updateBody();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link NetworkPolicy#limitBytes}.
+ */
+ public static class ConfirmLimitFragment extends DialogFragment {
+ private static final String EXTRA_MESSAGE_ID = "messageId";
+ private static final String EXTRA_LIMIT_BYTES = "limitBytes";
+
+ public static void show(DataUsageSummary parent) {
+ final Bundle args = new Bundle();
+
+ // TODO: customize default limits based on network template
+ final String currentTab = parent.mCurrentTab;
+ if (TAB_3G.equals(currentTab)) {
+ args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
+ args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+ } else if (TAB_4G.equals(currentTab)) {
+ args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
+ args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+ } else if (TAB_MOBILE.equals(currentTab)) {
+ args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
+ args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+ }
+
+ final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
+ final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_limit_dialog_title);
+ builder.setMessage(messageId);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setPolicyLimitBytes(limitBytes);
+ }
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#cycleDay}.
+ */
+ public static class CycleEditorFragment extends DialogFragment {
+ private static final String EXTRA_CYCLE_DAY = "cycleDay";
+
+ public static void show(DataUsageSummary parent) {
+ final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
+ final Bundle args = new Bundle();
+ args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
+
+ final CycleEditorFragment dialog = new CycleEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
+ final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
+
+ final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
+
+ cycleDayPicker.setMinValue(1);
+ cycleDayPicker.setMaxValue(31);
+ cycleDayPicker.setValue(oldCycleDay);
+ cycleDayPicker.setWrapSelectorWheel(true);
+
+ builder.setTitle(R.string.data_usage_cycle_editor_title);
+ builder.setView(view);
+
+ builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final int cycleDay = cycleDayPicker.getValue();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setPolicyCycleDay(cycleDay);
+ }
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link Settings.Secure#DATA_ROAMING}.
+ */
+ public static class ConfirmDataRoamingFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ final Bundle args = new Bundle();
+
+ final ConfirmDataRoamingFragment dialog = new ConfirmDataRoamingFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_ROAMING);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.roaming_reenable_title);
+ builder.setMessage(R.string.roaming_warning);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setDataRoaming(true);
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link INetworkPolicyManager#setRestrictBackground(boolean)}.
+ */
+ public static class ConfirmRestrictFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ final Bundle args = new Bundle();
+
+ final ConfirmRestrictFragment dialog = new ConfirmRestrictFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_RESTRICT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_restrict_background_title);
+ builder.setMessage(R.string.data_usage_restrict_background);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setRestrictBackground(true);
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to request user confirmation before setting
+ * {@link #POLICY_REJECT_METERED_BACKGROUND}.
+ */
+ public static class ConfirmAppRestrictFragment extends DialogFragment {
+ public static void show(DataUsageSummary parent) {
+ final ConfirmAppRestrictFragment dialog = new ConfirmAppRestrictFragment();
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_APP_RESTRICT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_app_restrict_dialog_title);
+ builder.setMessage(R.string.data_usage_app_restrict_dialog);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setAppRestrictBackground(true);
+ }
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Compute default tab that should be selected, based on
+ * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
+ */
+ private static String computeTabFromIntent(Intent intent) {
+ final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
+ if (template == null) return null;
+
+ switch (template.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER:
+ return TAB_3G;
+ case MATCH_MOBILE_4G:
+ return TAB_4G;
+ case MATCH_MOBILE_ALL:
+ return TAB_MOBILE;
+ case MATCH_WIFI:
+ return TAB_WIFI;
+ default:
+ return null;
+ }
+ }
+
+ public static class UidDetail {
+ public CharSequence label;
+ public CharSequence[] detailLabels;
+ public Drawable icon;
+ }
+
+ /**
+ * Resolve best descriptive label for the given UID.
+ */
+ public static UidDetail resolveDetailForUid(Context context, int uid) {
+ final Resources res = context.getResources();
+ final PackageManager pm = context.getPackageManager();
+
+ final UidDetail detail = new UidDetail();
+ detail.label = pm.getNameForUid(uid);
+ detail.icon = pm.getDefaultActivityIcon();
+
+ // handle special case labels
+ switch (uid) {
+ case android.os.Process.SYSTEM_UID:
+ detail.label = res.getString(R.string.process_kernel_label);
+ detail.icon = pm.getDefaultActivityIcon();
+ return detail;
+ case TrafficStats.UID_REMOVED:
+ detail.label = res.getString(R.string.data_usage_uninstalled_apps);
+ detail.icon = pm.getDefaultActivityIcon();
+ return detail;
+ }
+
+ // otherwise fall back to using packagemanager labels
+ final String[] packageNames = pm.getPackagesForUid(uid);
+ final int length = packageNames != null ? packageNames.length : 0;
+
+ try {
+ if (length == 1) {
+ final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
+ detail.label = info.loadLabel(pm).toString();
+ detail.icon = info.loadIcon(pm);
+ } else if (length > 1) {
+ detail.detailLabels = new CharSequence[length];
+ for (int i = 0; i < length; i++) {
+ final String packageName = packageNames[i];
+ final PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
+ final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
+
+ detail.detailLabels[i] = appInfo.loadLabel(pm).toString();
+ if (packageInfo.sharedUserLabel != 0) {
+ detail.label = pm.getText(packageName, packageInfo.sharedUserLabel,
+ packageInfo.applicationInfo).toString();
+ detail.icon = appInfo.loadIcon(pm);
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ }
+
+ if (TextUtils.isEmpty(detail.label)) {
+ detail.label = Integer.toString(uid);
+ }
+ return detail;
+ }
+
+ /**
+ * Test if device has a mobile data radio.
+ */
+ private static boolean hasMobileRadio(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("mobile");
+ }
+
+ final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+
+ // mobile devices should have MOBILE network tracker regardless of
+ // connection status.
+ return conn.getNetworkInfo(TYPE_MOBILE) != null;
+ }
+
+ /**
+ * Test if device has a mobile 4G data radio.
+ */
+ private static boolean hasMobile4gRadio(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("4g");
+ }
+
+ final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+
+ // WiMAX devices should have WiMAX network tracker regardless of
+ // connection status.
+ final boolean hasWimax = conn.getNetworkInfo(TYPE_WIMAX) != null;
+ final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
+ return hasWimax || hasLte;
+ }
+
+ /**
+ * Test if device has a Wi-Fi data radio.
+ */
+ private static boolean hasWifiRadio(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("wifi");
+ }
+
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
+ }
+
+ /**
+ * Test if device has an ethernet network connection.
+ */
+ private static boolean hasEthernet(Context context) {
+ if (TEST_RADIOS) {
+ return SystemProperties.get(TEST_RADIOS_PROP).contains("ethernet");
+ }
+
+ final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ return conn.getNetworkInfo(TYPE_ETHERNET) != null;
+ }
+
+ /**
+ * Inflate a {@link Preference} style layout, adding the given {@link View}
+ * widget into {@link android.R.id#widget_frame}.
+ */
+ private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
+ final View view = inflater.inflate(R.layout.preference, root, false);
+ final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
+ android.R.id.widget_frame);
+ widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ return view;
+ }
+
+ private static View inflateAppTitle(
+ LayoutInflater inflater, ViewGroup root, CharSequence label) {
+ final TextView view = (TextView) inflater.inflate(
+ R.layout.data_usage_app_title, root, false);
+ view.setText(label);
+ return view;
+ }
+
+ /**
+ * Set {@link android.R.id#title} for a preference view inflated with
+ * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
+ */
+ private static void setPreferenceTitle(View parent, int resId) {
+ final TextView title = (TextView) parent.findViewById(android.R.id.title);
+ title.setText(resId);
+ }
+
+ /**
+ * Set {@link android.R.id#summary} for a preference view inflated with
+ * {@link #inflatePreference(LayoutInflater, ViewGroup, View)}.
+ */
+ private static void setPreferenceSummary(View parent, int resId) {
+ final TextView summary = (TextView) parent.findViewById(android.R.id.summary);
+ summary.setVisibility(View.VISIBLE);
+ summary.setText(resId);
+ }
+}
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
index d2c5973..3935d59 100644
--- a/src/com/android/settings/DateTimeSettings.java
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -88,7 +88,6 @@ public class DateTimeSettings extends SettingsPreferenceFragment
boolean isFirstRun = intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
mDummyDate = Calendar.getInstance();
- mDummyDate.set(mDummyDate.get(Calendar.YEAR), 11, 31, 13, 0, 0);
mAutoTimePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME);
mAutoTimePref.setChecked(autoTimeEnabled);
@@ -171,6 +170,8 @@ public class DateTimeSettings extends SettingsPreferenceFragment
public void updateTimeAndDateDisplay(Context context) {
java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context);
final Calendar now = Calendar.getInstance();
+ mDummyDate.setTimeZone(now.getTimeZone());
+ mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0);
Date dummyDate = mDummyDate.getTime();
mTimePref.setSummary(DateFormat.getTimeFormat(getActivity()).format(now.getTime()));
mTimeZone.setSummary(getTimeZoneText(now.getTimeZone()));
@@ -353,6 +354,8 @@ public class DateTimeSettings extends SettingsPreferenceFragment
c.set(Calendar.HOUR_OF_DAY, hourOfDay);
c.set(Calendar.MINUTE, minute);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
long when = c.getTimeInMillis();
if (when / 1000 < Integer.MAX_VALUE) {
diff --git a/src/com/android/settings/DateTimeSettingsSetupWizard.java b/src/com/android/settings/DateTimeSettingsSetupWizard.java
index 4ff7fc8..e63153e 100644
--- a/src/com/android/settings/DateTimeSettingsSetupWizard.java
+++ b/src/com/android/settings/DateTimeSettingsSetupWizard.java
@@ -31,7 +31,6 @@ import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
-import android.text.format.DateFormat;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
@@ -46,6 +45,7 @@ import android.widget.DatePicker;
import android.widget.LinearLayout;
import android.widget.ListPopupWindow;
import android.widget.SimpleAdapter;
+import android.widget.TextView;
import android.widget.TimePicker;
import java.util.Calendar;
@@ -136,8 +136,6 @@ public class DateTimeSettingsSetupWizard extends Activity
mAutoDateTimeButton = (CompoundButton)findViewById(R.id.date_time_auto_button);
mAutoDateTimeButton.setChecked(autoDateTimeEnabled);
- mAutoDateTimeButton.setText(autoDateTimeEnabled ? R.string.date_time_auto_summaryOn :
- R.string.date_time_auto_summaryOff);
mAutoDateTimeButton.setOnCheckedChangeListener(this);
mTimePicker = (TimePicker)findViewById(R.id.time_picker);
diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java
index 0933d62..0801b1f 100644
--- a/src/com/android/settings/DefaultRingtonePreference.java
+++ b/src/com/android/settings/DefaultRingtonePreference.java
@@ -23,7 +23,6 @@ import android.media.RingtoneManager;
import android.net.Uri;
import android.preference.RingtonePreference;
import android.util.AttributeSet;
-import android.util.Config;
import android.util.Log;
public class DefaultRingtonePreference extends RingtonePreference {
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 2508454..2ca28e9 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -16,13 +16,20 @@
package com.android.settings;
+import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
@@ -31,6 +38,8 @@ import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceChangeListener;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.IWindowManager;
/*
* Displays preferences for application developers.
@@ -45,10 +54,37 @@ public class DevelopmentSettings extends PreferenceFragment
private static final String HDCP_CHECKING_KEY = "hdcp_checking";
private static final String HDCP_CHECKING_PROPERTY = "persist.sys.hdcp_checking";
+ private static final String STRICT_MODE_KEY = "strict_mode";
+ private static final String POINTER_LOCATION_KEY = "pointer_location";
+ private static final String SHOW_SCREEN_UPDATES_KEY = "show_screen_updates";
+ private static final String SHOW_CPU_USAGE_KEY = "show_cpu_usage";
+ private static final String WINDOW_ANIMATION_SCALE_KEY = "window_animation_scale";
+ private static final String TRANSITION_ANIMATION_SCALE_KEY = "transition_animation_scale";
+
+ private static final String IMMEDIATELY_DESTROY_ACTIVITIES_KEY
+ = "immediately_destroy_activities";
+ private static final String APP_PROCESS_LIMIT_KEY = "app_process_limit";
+
+ private static final String SHOW_ALL_ANRS_KEY = "show_all_anrs";
+
+ private IWindowManager mWindowManager;
+
private CheckBoxPreference mEnableAdb;
private CheckBoxPreference mKeepScreenOn;
private CheckBoxPreference mAllowMockLocation;
+ private CheckBoxPreference mStrictMode;
+ private CheckBoxPreference mPointerLocation;
+ private CheckBoxPreference mShowScreenUpdates;
+ private CheckBoxPreference mShowCpuUsage;
+ private ListPreference mWindowAnimationScale;
+ private ListPreference mTransitionAnimationScale;
+
+ private CheckBoxPreference mImmediatelyDestroyActivities;
+ private ListPreference mAppProcessLimit;
+
+ private CheckBoxPreference mShowAllANRs;
+
// To track whether Yes was clicked in the adb warning dialog
private boolean mOkClicked;
@@ -58,12 +94,31 @@ public class DevelopmentSettings extends PreferenceFragment
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+
addPreferencesFromResource(R.xml.development_prefs);
mEnableAdb = (CheckBoxPreference) findPreference(ENABLE_ADB);
mKeepScreenOn = (CheckBoxPreference) findPreference(KEEP_SCREEN_ON);
mAllowMockLocation = (CheckBoxPreference) findPreference(ALLOW_MOCK_LOCATION);
+ mStrictMode = (CheckBoxPreference) findPreference(STRICT_MODE_KEY);
+ mPointerLocation = (CheckBoxPreference) findPreference(POINTER_LOCATION_KEY);
+ mShowScreenUpdates = (CheckBoxPreference) findPreference(SHOW_SCREEN_UPDATES_KEY);
+ mShowCpuUsage = (CheckBoxPreference) findPreference(SHOW_CPU_USAGE_KEY);
+ mWindowAnimationScale = (ListPreference) findPreference(WINDOW_ANIMATION_SCALE_KEY);
+ mWindowAnimationScale.setOnPreferenceChangeListener(this);
+ mTransitionAnimationScale = (ListPreference) findPreference(TRANSITION_ANIMATION_SCALE_KEY);
+ mTransitionAnimationScale.setOnPreferenceChangeListener(this);
+
+ mImmediatelyDestroyActivities = (CheckBoxPreference) findPreference(
+ IMMEDIATELY_DESTROY_ACTIVITIES_KEY);
+ mAppProcessLimit = (ListPreference) findPreference(APP_PROCESS_LIMIT_KEY);
+ mAppProcessLimit.setOnPreferenceChangeListener(this);
+
+ mShowAllANRs = (CheckBoxPreference) findPreference(
+ SHOW_ALL_ANRS_KEY);
+
removeHdcpOptionsForProduction();
}
@@ -89,6 +144,14 @@ public class DevelopmentSettings extends PreferenceFragment
mAllowMockLocation.setChecked(Settings.Secure.getInt(cr,
Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0);
updateHdcpValues();
+ updateStrictModeVisualOptions();
+ updatePointerLocationOptions();
+ updateFlingerOptions();
+ updateCpuUsageOptions();
+ updateAnimationScaleOptions();
+ updateImmediatelyDestroyActivitiesOptions();
+ updateAppProcessLimitOptions();
+ updateShowAllANRsOptions();
}
private void updateHdcpValues() {
@@ -110,6 +173,182 @@ public class DevelopmentSettings extends PreferenceFragment
}
}
+ // Returns the current state of the system property that controls
+ // strictmode flashes. One of:
+ // 0: not explicitly set one way or another
+ // 1: on
+ // 2: off
+ private int currentStrictModeActiveIndex() {
+ if (TextUtils.isEmpty(SystemProperties.get(StrictMode.VISUAL_PROPERTY))) {
+ return 0;
+ }
+ boolean enabled = SystemProperties.getBoolean(StrictMode.VISUAL_PROPERTY, false);
+ return enabled ? 1 : 2;
+ }
+
+ private void writeStrictModeVisualOptions() {
+ try {
+ mWindowManager.setStrictModeVisualIndicatorPreference(mStrictMode.isChecked()
+ ? "1" : "");
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void updateStrictModeVisualOptions() {
+ mStrictMode.setChecked(currentStrictModeActiveIndex() == 1);
+ }
+
+ private void writePointerLocationOptions() {
+ Settings.System.putInt(getActivity().getContentResolver(),
+ Settings.System.POINTER_LOCATION, mPointerLocation.isChecked() ? 1 : 0);
+ }
+
+ private void updatePointerLocationOptions() {
+ mPointerLocation.setChecked(Settings.System.getInt(getActivity().getContentResolver(),
+ Settings.System.POINTER_LOCATION, 0) != 0);
+ }
+
+ private void updateFlingerOptions() {
+ // magic communication with surface flinger.
+ try {
+ IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ if (flinger != null) {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ flinger.transact(1010, data, reply, 0);
+ @SuppressWarnings("unused")
+ int showCpu = reply.readInt();
+ @SuppressWarnings("unused")
+ int enableGL = reply.readInt();
+ int showUpdates = reply.readInt();
+ mShowScreenUpdates.setChecked(showUpdates != 0);
+ @SuppressWarnings("unused")
+ int showBackground = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void writeFlingerOptions() {
+ try {
+ IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+ if (flinger != null) {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ data.writeInt(mShowScreenUpdates.isChecked() ? 1 : 0);
+ flinger.transact(1002, data, null, 0);
+ data.recycle();
+
+ updateFlingerOptions();
+ }
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void updateCpuUsageOptions() {
+ mShowCpuUsage.setChecked(Settings.System.getInt(getActivity().getContentResolver(),
+ Settings.System.SHOW_PROCESSES, 0) != 0);
+ }
+
+ private void writeCpuUsageOptions() {
+ boolean value = mShowCpuUsage.isChecked();
+ Settings.System.putInt(getActivity().getContentResolver(),
+ Settings.System.SHOW_PROCESSES, value ? 1 : 0);
+ Intent service = (new Intent())
+ .setClassName("com.android.systemui", "com.android.systemui.LoadAverageService");
+ if (value) {
+ getActivity().startService(service);
+ } else {
+ getActivity().stopService(service);
+ }
+ }
+
+ private void writeImmediatelyDestroyActivitiesOptions() {
+ try {
+ ActivityManagerNative.getDefault().setAlwaysFinish(
+ mImmediatelyDestroyActivities.isChecked());
+ } catch (RemoteException ex) {
+ }
+ }
+
+ private void updateImmediatelyDestroyActivitiesOptions() {
+ mImmediatelyDestroyActivities.setChecked(Settings.System.getInt(
+ getActivity().getContentResolver(), Settings.System.ALWAYS_FINISH_ACTIVITIES, 0) != 0);
+ }
+
+ private void updateAnimationScaleValue(int which, ListPreference pref) {
+ try {
+ float scale = mWindowManager.getAnimationScale(which);
+ CharSequence[] values = pref.getEntryValues();
+ for (int i=0; i<values.length; i++) {
+ float val = Float.parseFloat(values[i].toString());
+ if (scale <= val) {
+ pref.setValueIndex(i);
+ pref.setSummary(pref.getEntries()[i]);
+ return;
+ }
+ }
+ pref.setValueIndex(values.length-1);
+ pref.setSummary(pref.getEntries()[0]);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void updateAnimationScaleOptions() {
+ updateAnimationScaleValue(0, mWindowAnimationScale);
+ updateAnimationScaleValue(1, mTransitionAnimationScale);
+ }
+
+ private void writeAnimationScaleOption(int which, ListPreference pref, Object newValue) {
+ try {
+ float scale = Float.parseFloat(newValue.toString());
+ mWindowManager.setAnimationScale(which, scale);
+ updateAnimationScaleValue(which, pref);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void updateAppProcessLimitOptions() {
+ try {
+ int limit = ActivityManagerNative.getDefault().getProcessLimit();
+ CharSequence[] values = mAppProcessLimit.getEntryValues();
+ for (int i=0; i<values.length; i++) {
+ int val = Integer.parseInt(values[i].toString());
+ if (val >= limit) {
+ mAppProcessLimit.setValueIndex(i);
+ mAppProcessLimit.setSummary(mAppProcessLimit.getEntries()[i]);
+ return;
+ }
+ }
+ mAppProcessLimit.setValueIndex(0);
+ mAppProcessLimit.setSummary(mAppProcessLimit.getEntries()[0]);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void writeAppProcessLimitOptions(Object newValue) {
+ try {
+ int limit = Integer.parseInt(newValue.toString());
+ ActivityManagerNative.getDefault().setProcessLimit(limit);
+ updateAppProcessLimitOptions();
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void writeShowAllANRsOptions() {
+ Settings.Secure.putInt(getActivity().getContentResolver(),
+ Settings.Secure.ANR_SHOW_BACKGROUND,
+ mShowAllANRs.isChecked() ? 1 : 0);
+ }
+
+ private void updateShowAllANRsOptions() {
+ mShowAllANRs.setChecked(Settings.Secure.getInt(
+ getActivity().getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0);
+ }
+
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
@@ -142,11 +381,42 @@ public class DevelopmentSettings extends PreferenceFragment
Settings.Secure.putInt(getActivity().getContentResolver(),
Settings.Secure.ALLOW_MOCK_LOCATION,
mAllowMockLocation.isChecked() ? 1 : 0);
+ } else if (preference == mStrictMode) {
+ writeStrictModeVisualOptions();
+ } else if (preference == mPointerLocation) {
+ writePointerLocationOptions();
+ } else if (preference == mShowScreenUpdates) {
+ writeFlingerOptions();
+ } else if (preference == mShowCpuUsage) {
+ writeCpuUsageOptions();
+ } else if (preference == mImmediatelyDestroyActivities) {
+ writeImmediatelyDestroyActivitiesOptions();
+ } else if (preference == mShowAllANRs) {
+ writeShowAllANRsOptions();
}
return false;
}
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (HDCP_CHECKING_KEY.equals(preference.getKey())) {
+ SystemProperties.set(HDCP_CHECKING_PROPERTY, newValue.toString());
+ updateHdcpValues();
+ return true;
+ } else if (preference == mWindowAnimationScale) {
+ writeAnimationScaleOption(0, mWindowAnimationScale, newValue);
+ return true;
+ } else if (preference == mTransitionAnimationScale) {
+ writeAnimationScaleOption(1, mTransitionAnimationScale, newValue);
+ return true;
+ } else if (preference == mAppProcessLimit) {
+ writeAppProcessLimitOptions(newValue);
+ return true;
+ }
+ return false;
+ }
+
private void dismissDialog() {
if (mOkDialog == null) return;
mOkDialog.dismiss();
@@ -176,14 +446,4 @@ public class DevelopmentSettings extends PreferenceFragment
dismissDialog();
super.onDestroy();
}
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (HDCP_CHECKING_KEY.equals(preference.getKey())) {
- SystemProperties.set(HDCP_CHECKING_PROPERTY, newValue.toString());
- updateHdcpValues();
- return true;
- }
- return false;
- }
}
diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java
index d041c05..76f5a8e 100644
--- a/src/com/android/settings/DeviceInfoSettings.java
+++ b/src/com/android/settings/DeviceInfoSettings.java
@@ -40,7 +40,7 @@ import java.util.regex.Pattern;
public class DeviceInfoSettings extends SettingsPreferenceFragment {
- private static final String TAG = "DeviceInfoSettings";
+ private static final String LOG_TAG = "DeviceInfoSettings";
private static final String KEY_CONTAINER = "container";
private static final String KEY_TEAM = "team";
@@ -55,8 +55,6 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
private static final String KEY_DEVICE_MODEL = "device_model";
private static final String KEY_BASEBAND_VERSION = "baseband_version";
private static final String KEY_FIRMWARE_VERSION = "firmware_version";
- private static final String KEY_EQUIPMENT_ID = "fcc_equipment_id";
- private static final String PROPERTY_EQUIPMENT_ID = "ro.ril.fccid";
long[] mHits = new long[3];
@@ -80,7 +78,6 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
setStringSummary(KEY_FIRMWARE_VERSION, Build.VERSION.RELEASE);
findPreference(KEY_FIRMWARE_VERSION).setEnabled(true);
setValueSummary(KEY_BASEBAND_VERSION, "gsm.version.baseband");
- setValueSummary(KEY_EQUIPMENT_ID, PROPERTY_EQUIPMENT_ID);
setStringSummary(KEY_DEVICE_MODEL, Build.MODEL);
setStringSummary(KEY_BUILD_NUMBER, Build.DISPLAY);
findPreference(KEY_KERNEL_VERSION).setSummary(getFormattedKernelVersion());
@@ -89,11 +86,6 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
removePreferenceIfPropertyMissing(getPreferenceScreen(), "safetylegal",
PROPERTY_URL_SAFETYLEGAL);
- // Remove Equipment id preference if FCC ID is not set by RIL
- removePreferenceIfPropertyMissing(getPreferenceScreen(), KEY_EQUIPMENT_ID,
- PROPERTY_EQUIPMENT_ID);
-
-
// Remove Baseband version if wifi-only device
if (Utils.isWifiOnly()) {
getPreferenceScreen().removePreference(findPreference(KEY_BASEBAND_VERSION));
@@ -136,6 +128,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
try {
startActivity(intent);
} catch (Exception e) {
+ Log.e(LOG_TAG, "Unable to start activity " + intent.toString());
}
}
}
@@ -150,7 +143,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
try {
preferenceGroup.removePreference(findPreference(preference));
} catch (RuntimeException e) {
- Log.d(TAG, "Property '" + property + "' missing and no '"
+ Log.d(LOG_TAG, "Property '" + property + "' missing and no '"
+ preference + "' preference");
}
}
@@ -171,7 +164,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
SystemProperties.get(property,
getResources().getString(R.string.device_info_default)));
} catch (RuntimeException e) {
-
+ // No recovery
}
}
@@ -200,10 +193,10 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
Matcher m = p.matcher(procVersionStr);
if (!m.matches()) {
- Log.e(TAG, "Regex did not match on /proc/version: " + procVersionStr);
+ Log.e(LOG_TAG, "Regex did not match on /proc/version: " + procVersionStr);
return "Unavailable";
} else if (m.groupCount() < 4) {
- Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
+ Log.e(LOG_TAG, "Regex match on /proc/version only returned " + m.groupCount()
+ " groups");
return "Unavailable";
} else {
@@ -212,7 +205,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment {
.append(m.group(4))).toString();
}
} catch (IOException e) {
- Log.e(TAG,
+ Log.e(LOG_TAG,
"IO Exception when getting kernel version for Device Info screen",
e);
diff --git a/src/com/android/settings/Display.java b/src/com/android/settings/Display.java
index f90e0f0..fa29318 100644
--- a/src/com/android/settings/Display.java
+++ b/src/com/android/settings/Display.java
@@ -105,7 +105,7 @@ public class Display extends Activity implements View.OnClickListener {
public void onClick(View v) {
try {
- ActivityManagerNative.getDefault().updateConfiguration(mCurConfig);
+ ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
} catch (RemoteException e) {
}
finish();
diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
index cdb0147..7520ab3 100644
--- a/src/com/android/settings/DisplaySettings.java
+++ b/src/com/android/settings/DisplaySettings.java
@@ -18,21 +18,22 @@ package com.android.settings;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import android.app.ActivityManagerNative;
import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.util.Log;
-import android.view.IWindowManager;
import java.util.ArrayList;
@@ -44,15 +45,14 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
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 static final String KEY_FONT_SIZE = "font_size";
- private ListPreference mAnimations;
private CheckBoxPreference mAccelerometer;
- private float[] mAnimationScales;
-
- private IWindowManager mWindowManager;
+ private ListPreference mFontSizePref;
+ private final Configuration mCurConfig = new Configuration();
+
private ListPreference mScreenTimeoutPreference;
private ContentObserver mAccelerometerRotationObserver = new ContentObserver(new Handler()) {
@@ -66,12 +66,9 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ContentResolver resolver = getActivity().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);
@@ -81,22 +78,41 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
mScreenTimeoutPreference.setValue(String.valueOf(currentTimeout));
mScreenTimeoutPreference.setOnPreferenceChangeListener(this);
disableUnusableTimeouts(mScreenTimeoutPreference);
- updateTimeoutPreferenceDescription(resolver, currentTimeout);
+ updateTimeoutPreferenceDescription(mScreenTimeoutPreference,
+ R.string.screen_timeout_summary, currentTimeout);
+
+ mFontSizePref = (ListPreference) findPreference(KEY_FONT_SIZE);
+ mFontSizePref.setOnPreferenceChangeListener(this);
}
- private void updateTimeoutPreferenceDescription(ContentResolver resolver, long currentTimeout) {
- final CharSequence[] entries = mScreenTimeoutPreference.getEntries();
- final CharSequence[] values = mScreenTimeoutPreference.getEntryValues();
- int best = 0;
- for (int i = 0; i < values.length; i++) {
- long timeout = Long.valueOf(values[i].toString());
- if (currentTimeout >= timeout) {
- best = i;
+ private void updateTimeoutPreferenceDescription(
+ ListPreference pref,
+ int summaryStrings,
+ long currentTimeout) {
+ updateTimeoutPreferenceDescription(pref, summaryStrings, 0, currentTimeout);
+ }
+
+ private void updateTimeoutPreferenceDescription(
+ ListPreference pref,
+ int summaryStrings,
+ int zeroString,
+ long currentTimeout) {
+ String summary;
+ if (currentTimeout == 0) {
+ summary = pref.getContext().getString(zeroString);
+ } else {
+ final CharSequence[] entries = pref.getEntries();
+ final CharSequence[] values = pref.getEntryValues();
+ int best = 0;
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.valueOf(values[i].toString());
+ if (currentTimeout >= timeout) {
+ best = i;
+ }
}
+ summary = pref.getContext().getString(summaryStrings, entries[best]);
}
- String summary = mScreenTimeoutPreference.getContext()
- .getString(R.string.screen_timeout_summary, entries[best]);
- mScreenTimeoutPreference.setSummary(summary);
+ pref.setSummary(summary);
}
private void disableUnusableTimeouts(ListPreference screenTimeoutPreference) {
@@ -135,11 +151,42 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
screenTimeoutPreference.setEnabled(revisedEntries.size() > 0);
}
+ int floatToIndex(float val) {
+ String[] indices = getResources().getStringArray(R.array.entryvalues_font_size);
+ float lastVal = Float.parseFloat(indices[0]);
+ for (int i=1; i<indices.length; i++) {
+ float thisVal = Float.parseFloat(indices[i]);
+ if (val < (lastVal + (thisVal-lastVal)*.5f)) {
+ return i-1;
+ }
+ lastVal = thisVal;
+ }
+ return indices.length-1;
+ }
+
+ public void readFontSizePreference(ListPreference pref) {
+ try {
+ mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to retrieve font size");
+ }
+
+ // mark the appropriate item in the preferences list
+ int index = floatToIndex(mCurConfig.fontScale);
+ pref.setValueIndex(index);
+
+ // report the current size in the summary text
+ final Resources res = getResources();
+ String[] fontSizeNames = res.getStringArray(R.array.entries_font_size);
+ pref.setSummary(String.format(res.getString(R.string.summary_font_size),
+ fontSizeNames[index]));
+ }
+
@Override
public void onResume() {
super.onResume();
- updateState(true);
+ updateState();
getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), true,
mAccelerometerRotationObserver);
@@ -152,33 +199,9 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
getContentResolver().unregisterContentObserver(mAccelerometerRotationObserver);
}
- 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());
+ private void updateState() {
updateAccelerometerRotationCheckbox();
+ readFontSizePreference(mFontSizePref);
}
private void updateAccelerometerRotationCheckbox() {
@@ -187,19 +210,15 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
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;
- }
+ public void writeFontSizePreference(Object objValue) {
+ try {
+ mCurConfig.fontScale = Float.parseFloat(objValue.toString());
+ ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to save font size");
}
}
-
+
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if (preference == mAccelerometer) {
@@ -207,40 +226,25 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
Settings.System.ACCELEROMETER_ROTATION,
mAccelerometer.isChecked() ? 1 : 0);
}
- return true;
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
}
public boolean onPreferenceChange(Preference preference, Object objValue) {
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);
- updateTimeoutPreferenceDescription(getContentResolver(), value);
+ updateTimeoutPreferenceDescription(mScreenTimeoutPreference,
+ R.string.screen_timeout_summary, value);
} catch (NumberFormatException e) {
Log.e(TAG, "could not persist screen timeout setting", e);
}
}
+ if (KEY_FONT_SIZE.equals(key)) {
+ writeFontSizePreference(objValue);
+ }
return true;
}
diff --git a/src/com/android/settings/DreamComponentPreference.java b/src/com/android/settings/DreamComponentPreference.java
new file mode 100644
index 0000000..3bc0eb4
--- /dev/null
+++ b/src/com/android/settings/DreamComponentPreference.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.provider.Settings.Secure.DREAM_COMPONENT;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.preference.Preference;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DreamComponentPreference extends Preference {
+ private static final String TAG = "DreamComponentPreference";
+
+ private final PackageManager pm;
+ private final ContentResolver resolver;
+
+ public DreamComponentPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ pm = getContext().getPackageManager();
+ resolver = getContext().getContentResolver();
+
+ refreshFromSettings();
+ }
+
+ private void refreshFromSettings() {
+ String component = Settings.Secure.getString(resolver, DREAM_COMPONENT);
+ if (component != null) {
+ ComponentName cn = ComponentName.unflattenFromString(component);
+ try {
+ setSummary(pm.getActivityInfo(cn, 0).loadLabel(pm));
+ } catch (PackageManager.NameNotFoundException ex) {
+ setSummary("(unknown)");
+ }
+ }
+ }
+
+ public class DreamListAdapter extends BaseAdapter implements ListAdapter {
+ private ArrayList<ResolveInfo> results;
+ private final LayoutInflater inflater;
+
+ public DreamListAdapter(Context context) {
+ Intent choosy = new Intent(Intent.ACTION_MAIN)
+ .addCategory("android.intent.category.DREAM");
+
+ inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ results = new ArrayList<ResolveInfo>(pm.queryIntentActivities(choosy, 0));
+ }
+
+ @Override
+ public int getCount() {
+ return results.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return results.get(position);
+ }
+
+ @Override
+ public long getItemId (int position) {
+ return (long) position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = (convertView != null)
+ ? convertView
+ : inflater.inflate(R.layout.dream_picker_row, parent, false);
+ ResolveInfo ri = results.get(position);
+ ((TextView)row.findViewById(R.id.title)).setText(ri.loadLabel(pm));
+ ((ImageView)row.findViewById(R.id.icon)).setImageDrawable(ri.loadIcon(pm));
+ return row;
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ final DreamListAdapter list = new DreamListAdapter(getContext());
+ AlertDialog alert = new AlertDialog.Builder(getContext())
+ .setAdapter(
+ list,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ResolveInfo ri = (ResolveInfo)list.getItem(which);
+ ActivityInfo act = ri.activityInfo;
+ ComponentName cn = new ComponentName(
+ act.applicationInfo.packageName,
+ act.name);
+ Intent intent = new Intent(Intent.ACTION_MAIN).setComponent(cn);
+
+ setSummary(ri.loadLabel(pm));
+ //getContext().startActivity(intent);
+
+ Settings.Secure.putString(resolver, DREAM_COMPONENT, cn.flattenToString());
+ }
+ })
+ .create();
+ alert.show();
+ }
+}
diff --git a/src/com/android/settings/DreamSettings.java b/src/com/android/settings/DreamSettings.java
new file mode 100644
index 0000000..7ddab31
--- /dev/null
+++ b/src/com/android/settings/DreamSettings.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.provider.Settings.Secure.DREAM_TIMEOUT;
+
+import android.app.ActivityManagerNative;
+import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.IWindowManager;
+
+import java.util.ArrayList;
+
+public class DreamSettings extends SettingsPreferenceFragment implements
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = "DreamSettings";
+
+ private static final String KEY_DREAM_TIMEOUT = "dream_timeout";
+
+ private ListPreference mScreenTimeoutPreference;
+ private ListPreference mDreamTimeoutPreference;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ContentResolver resolver = getActivity().getContentResolver();
+
+ addPreferencesFromResource(R.xml.dream_settings);
+
+ mDreamTimeoutPreference = (ListPreference) findPreference(KEY_DREAM_TIMEOUT);
+ final long currentSaverTimeout = Settings.Secure.getLong(resolver, DREAM_TIMEOUT,
+ 0);
+ mDreamTimeoutPreference.setValue(String.valueOf(currentSaverTimeout));
+ mDreamTimeoutPreference.setOnPreferenceChangeListener(this);
+ updateTimeoutPreferenceDescription(resolver, mDreamTimeoutPreference,
+ R.string.dream_timeout_summary,
+ R.string.dream_timeout_zero_summary,
+ currentSaverTimeout);
+ }
+
+ private void updateTimeoutPreferenceDescription(
+ ContentResolver resolver,
+ ListPreference pref,
+ int summaryStrings,
+ long currentTimeout) {
+ updateTimeoutPreferenceDescription(resolver, pref, summaryStrings, 0, currentTimeout);
+ }
+ private void updateTimeoutPreferenceDescription(
+ ContentResolver resolver,
+ ListPreference pref,
+ int summaryStrings,
+ int zeroString,
+ long currentTimeout) {
+ String summary;
+ if (currentTimeout == 0) {
+ summary = pref.getContext().getString(zeroString);
+ } else {
+ final CharSequence[] entries = pref.getEntries();
+ final CharSequence[] values = pref.getEntryValues();
+ int best = 0;
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.valueOf(values[i].toString());
+ if (currentTimeout >= timeout) {
+ best = i;
+ }
+ }
+ summary = pref.getContext().getString(summaryStrings, entries[best]);
+ }
+ pref.setSummary(summary);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ final String key = preference.getKey();
+ if (KEY_DREAM_TIMEOUT.equals(key)) {
+ int value = Integer.parseInt((String) objValue);
+ try {
+ Settings.Secure.putInt(getContentResolver(),
+ DREAM_TIMEOUT, value);
+ updateTimeoutPreferenceDescription(getContentResolver(),
+ mDreamTimeoutPreference,
+ R.string.dream_timeout_summary,
+ R.string.dream_timeout_zero_summary,
+ value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist dream timeout setting", e);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/android/settings/DreamTesterPreference.java b/src/com/android/settings/DreamTesterPreference.java
new file mode 100644
index 0000000..7ff5667
--- /dev/null
+++ b/src/com/android/settings/DreamTesterPreference.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.provider.Settings.Secure.DREAM_COMPONENT;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.preference.Preference;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DreamTesterPreference extends Preference {
+ private static final String TAG = "DreamTesterPreference";
+
+ private final PackageManager pm;
+ private final ContentResolver resolver;
+
+ public DreamTesterPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ pm = getContext().getPackageManager();
+ resolver = getContext().getContentResolver();
+ }
+
+ @Override
+ protected void onClick() {
+ String component = Settings.Secure.getString(resolver, DREAM_COMPONENT);
+ if (component != null) {
+ ComponentName cn = ComponentName.unflattenFromString(component);
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .setComponent(cn)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ .putExtra("android.dreams.TEST", true);
+ getContext().startActivity(intent);
+ }
+ }
+}
diff --git a/src/com/android/settings/GoogleLocationSettingHelper.java b/src/com/android/settings/GoogleLocationSettingHelper.java
index 0d4861e..be4a02c 100644
--- a/src/com/android/settings/GoogleLocationSettingHelper.java
+++ b/src/com/android/settings/GoogleLocationSettingHelper.java
@@ -122,6 +122,7 @@ public class GoogleLocationSettingHelper {
try {
context.startActivity(i);
} catch (ActivityNotFoundException e) {
+ Log.e("GoogleLocationSettingHelper", "Problem while starting GSF location activity");
}
}
diff --git a/src/com/android/settings/LocationSettings.java b/src/com/android/settings/LocationSettings.java
new file mode 100644
index 0000000..0824aab
--- /dev/null
+++ b/src/com/android/settings/LocationSettings.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.location.LocationManager;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+
+import java.util.Observable;
+import java.util.Observer;
+
+/**
+ * Gesture lock pattern settings.
+ */
+public class LocationSettings extends SettingsPreferenceFragment
+ implements OnPreferenceChangeListener {
+
+ // Location Settings
+ private static final String KEY_LOCATION_NETWORK = "location_network";
+ private static final String KEY_LOCATION_GPS = "location_gps";
+ private static final String KEY_ASSISTED_GPS = "assisted_gps";
+ private static final String KEY_USE_LOCATION = "location_use_for_services";
+
+ private CheckBoxPreference mNetwork;
+ private CheckBoxPreference mGps;
+ private CheckBoxPreference mAssistedGps;
+ private CheckBoxPreference mUseLocation;
+
+ // These provide support for receiving notification when Location Manager settings change.
+ // This is necessary because the Network Location Provider can change settings
+ // if the user does not confirm enabling the provider.
+ private ContentQueryMap mContentQueryMap;
+
+ private Observer mSettingsObserver;
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // listen for Location Manager settings changes
+ Cursor settingsCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, null,
+ "(" + Settings.System.NAME + "=?)",
+ new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED},
+ null);
+ mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mSettingsObserver != null) {
+ mContentQueryMap.deleteObserver(mSettingsObserver);
+ }
+ }
+
+ private PreferenceScreen createPreferenceHierarchy() {
+ PreferenceScreen root = getPreferenceScreen();
+ if (root != null) {
+ root.removeAll();
+ }
+ addPreferencesFromResource(R.xml.location_settings);
+ root = getPreferenceScreen();
+
+ mNetwork = (CheckBoxPreference) root.findPreference(KEY_LOCATION_NETWORK);
+ mGps = (CheckBoxPreference) root.findPreference(KEY_LOCATION_GPS);
+ mAssistedGps = (CheckBoxPreference) root.findPreference(KEY_ASSISTED_GPS);
+ if (GoogleLocationSettingHelper.isAvailable(getActivity())) {
+ // GSF present, Add setting for 'Use My Location'
+ CheckBoxPreference useLocation = new CheckBoxPreference(getActivity());
+ useLocation.setKey(KEY_USE_LOCATION);
+ useLocation.setTitle(R.string.use_location_title);
+ useLocation.setSummary(R.string.use_location_summary);
+ useLocation.setChecked(
+ GoogleLocationSettingHelper.getUseLocationForServices(getActivity())
+ == GoogleLocationSettingHelper.USE_LOCATION_FOR_SERVICES_ON);
+ useLocation.setPersistent(false);
+ useLocation.setOnPreferenceChangeListener(this);
+ getPreferenceScreen().addPreference(useLocation);
+ mUseLocation = useLocation;
+ }
+
+ // Change the summary for wifi-only devices
+ if (Utils.isWifiOnly()) {
+ mNetwork.setSummaryOn(R.string.location_neighborhood_level_wifi);
+ }
+
+ return root;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Make sure we reload the preference hierarchy since some of these settings
+ // depend on others...
+ createPreferenceHierarchy();
+ updateLocationToggles();
+
+ if (mSettingsObserver == null) {
+ mSettingsObserver = new Observer() {
+ public void update(Observable o, Object arg) {
+ updateLocationToggles();
+ }
+ };
+ mContentQueryMap.addObserver(mSettingsObserver);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+
+ if (preference == mNetwork) {
+ Settings.Secure.setLocationProviderEnabled(getContentResolver(),
+ LocationManager.NETWORK_PROVIDER, mNetwork.isChecked());
+ } else if (preference == mGps) {
+ boolean enabled = mGps.isChecked();
+ Settings.Secure.setLocationProviderEnabled(getContentResolver(),
+ LocationManager.GPS_PROVIDER, enabled);
+ if (mAssistedGps != null) {
+ mAssistedGps.setEnabled(enabled);
+ }
+ } else if (preference == mAssistedGps) {
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED,
+ mAssistedGps.isChecked() ? 1 : 0);
+ } else {
+ // If we didn't handle it, let preferences handle it.
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ return true;
+ }
+
+ /*
+ * Creates toggles for each available location provider
+ */
+ private void updateLocationToggles() {
+ ContentResolver res = getContentResolver();
+ boolean gpsEnabled = Settings.Secure.isLocationProviderEnabled(
+ res, LocationManager.GPS_PROVIDER);
+ mNetwork.setChecked(Settings.Secure.isLocationProviderEnabled(
+ res, LocationManager.NETWORK_PROVIDER));
+ mGps.setChecked(gpsEnabled);
+ if (mAssistedGps != null) {
+ mAssistedGps.setChecked(Settings.Secure.getInt(res,
+ Settings.Secure.ASSISTED_GPS_ENABLED, 2) == 1);
+ mAssistedGps.setEnabled(gpsEnabled);
+ }
+ }
+
+ /**
+ * see confirmPatternThenDisableAndClear
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ createPreferenceHierarchy();
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ if (preference == mUseLocation) {
+ boolean newValue = (value == null ? false : (Boolean) value);
+ GoogleLocationSettingHelper.setUseLocationForServices(getActivity(), newValue);
+ // We don't want to change the value immediately here, since the user may click
+ // disagree in the dialog that pops up. When the activity we just launched exits, this
+ // activity will be restated and the new value re-read, so the checkbox will get its
+ // new value then.
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java
index 1b045ea..29a92b1 100644
--- a/src/com/android/settings/MasterClear.java
+++ b/src/com/android/settings/MasterClear.java
@@ -196,13 +196,14 @@ public class MasterClear extends Fragment {
+ " type=" + account.type);
continue;
}
- Drawable icon;
+ Drawable icon = null;
try {
- Context authContext = context.createPackageContext(desc.packageName, 0);
- icon = authContext.getResources().getDrawable(desc.iconId);
+ if (desc.iconId != 0) {
+ Context authContext = context.createPackageContext(desc.packageName, 0);
+ icon = authContext.getResources().getDrawable(desc.iconId);
+ }
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "No icon for account type " + desc.type);
- icon = null;
}
TextView child = (TextView)inflater.inflate(R.layout.master_clear_account,
diff --git a/src/com/android/settings/PointerSpeedPreference.java b/src/com/android/settings/PointerSpeedPreference.java
index e9704fd..6a6a71f 100644
--- a/src/com/android/settings/PointerSpeedPreference.java
+++ b/src/com/android/settings/PointerSpeedPreference.java
@@ -25,7 +25,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.preference.SeekBarPreference;
+import android.preference.SeekBarDialogPreference;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.AttributeSet;
@@ -33,7 +33,7 @@ import android.view.IWindowManager;
import android.view.View;
import android.widget.SeekBar;
-public class PointerSpeedPreference extends SeekBarPreference implements
+public class PointerSpeedPreference extends SeekBarDialogPreference implements
SeekBar.OnSeekBarChangeListener {
private SeekBar mSeekBar;
diff --git a/src/com/android/settings/PrivacySettings.java b/src/com/android/settings/PrivacySettings.java
index 28dc93a..6759500 100644
--- a/src/com/android/settings/PrivacySettings.java
+++ b/src/com/android/settings/PrivacySettings.java
@@ -43,11 +43,13 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
private static final String BACKUP_DATA = "backup_data";
private static final String AUTO_RESTORE = "auto_restore";
private static final String CONFIGURE_ACCOUNT = "configure_account";
+ private static final String LOCAL_BACKUP_PASSWORD = "local_backup_password";
private IBackupManager mBackupManager;
private CheckBoxPreference mBackup;
private CheckBoxPreference mAutoRestore;
private Dialog mConfirmDialog;
private PreferenceScreen mConfigure;
+ private PreferenceScreen mPassword;
private static final int DIALOG_ERASE_BACKUP = 2;
private int mDialogType;
@@ -64,6 +66,7 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
mBackup = (CheckBoxPreference) screen.findPreference(BACKUP_DATA);
mAutoRestore = (CheckBoxPreference) screen.findPreference(AUTO_RESTORE);
mConfigure = (PreferenceScreen) screen.findPreference(CONFIGURE_ACCOUNT);
+ mPassword = (PreferenceScreen) screen.findPreference(LOCAL_BACKUP_PASSWORD);
// Vendor specific
if (getActivity().getPackageManager().
@@ -158,7 +161,9 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
mConfigure.setEnabled(configureEnabled);
mConfigure.setIntent(configIntent);
setConfigureSummary(configSummary);
- }
+
+ updatePasswordSummary();
+}
private void setConfigureSummary(String summary) {
if (summary != null) {
@@ -178,6 +183,18 @@ public class PrivacySettings extends SettingsPreferenceFragment implements
}
}
+ private void updatePasswordSummary() {
+ try {
+ if (mBackupManager.hasBackupPassword()) {
+ mPassword.setSummary(R.string.local_backup_password_summary_change);
+ } else {
+ mPassword.setSummary(R.string.local_backup_password_summary_none);
+ }
+ } catch (RemoteException e) {
+ // Not much we can do here
+ }
+ }
+
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
//updateProviders();
diff --git a/src/com/android/settings/ProgressCategory.java b/src/com/android/settings/ProgressCategory.java
index c5b68b6..eee19bc 100644
--- a/src/com/android/settings/ProgressCategory.java
+++ b/src/com/android/settings/ProgressCategory.java
@@ -17,13 +17,15 @@
package com.android.settings;
import android.content.Context;
+import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.TextView;
public class ProgressCategory extends ProgressCategoryBase {
private boolean mProgress = false;
- private View oldView = null;
+ private Preference mNoDeviceFoundPreference;
public ProgressCategory(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -33,19 +35,27 @@ public class ProgressCategory extends ProgressCategoryBase {
@Override
public void onBindView(View view) {
super.onBindView(view);
- final View textView = view.findViewById(R.id.scanning_text);
+ final TextView textView = (TextView) view.findViewById(R.id.scanning_text);
final View progressBar = view.findViewById(R.id.scanning_progress);
- final int visibility = mProgress ? View.VISIBLE : View.INVISIBLE;
- textView.setVisibility(visibility);
- progressBar.setVisibility(visibility);
+ textView.setText(mProgress ? R.string.progress_scanning : R.string.progress_tap_to_pair);
+ boolean noDeviceFound = getPreferenceCount() == 0;
+ textView.setVisibility(noDeviceFound ? View.INVISIBLE : View.VISIBLE);
+ progressBar.setVisibility(mProgress ? View.VISIBLE : View.INVISIBLE);
- if (oldView != null) {
- oldView.findViewById(R.id.scanning_progress).setVisibility(View.GONE);
- oldView.findViewById(R.id.scanning_text).setVisibility(View.GONE);
- oldView.setVisibility(View.GONE);
+ if (mProgress) {
+ if (mNoDeviceFoundPreference != null) {
+ removePreference(mNoDeviceFoundPreference);
+ }
+ } else {
+ if (noDeviceFound) {
+ if (mNoDeviceFoundPreference == null) {
+ mNoDeviceFoundPreference = new Preference(getContext());
+ mNoDeviceFoundPreference.setSummary(R.string.bluetooth_no_devices_found);
+ }
+ addPreference(mNoDeviceFoundPreference);
+ }
}
- oldView = view;
}
@Override
diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java
index ba07fb5..d45616e 100644
--- a/src/com/android/settings/RadioInfo.java
+++ b/src/com/android/settings/RadioInfo.java
@@ -24,11 +24,11 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
+import android.net.TrafficStats;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
-import android.os.INetStatService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -133,7 +133,6 @@ public class RadioInfo extends Activity {
private TelephonyManager mTelephonyManager;
private Phone phone = null;
private PhoneStateIntentReceiver mPhoneStateReceiver;
- private INetStatService netstat;
private String mPingIpAddrResult;
private String mPingHostnameResult;
@@ -311,8 +310,6 @@ public class RadioInfo extends Activity {
phone.getNeighboringCids(
mHandler.obtainMessage(EVENT_QUERY_NEIGHBORING_CIDS_DONE));
- netstat = INetStatService.Stub.asInterface(ServiceManager.getService("netstat"));
-
CellLocation.requestLocationUpdate();
}
@@ -625,19 +622,16 @@ public class RadioInfo extends Activity {
private final void updateDataStats2() {
Resources r = getResources();
- try {
- long txPackets = netstat.getMobileTxPackets();
- long rxPackets = netstat.getMobileRxPackets();
- long txBytes = netstat.getMobileTxBytes();
- long rxBytes = netstat.getMobileRxBytes();
+ long txPackets = TrafficStats.getMobileTxPackets();
+ long rxPackets = TrafficStats.getMobileRxPackets();
+ long txBytes = TrafficStats.getMobileTxBytes();
+ long rxBytes = TrafficStats.getMobileRxBytes();
- String packets = r.getString(R.string.radioInfo_display_packets);
- String bytes = r.getString(R.string.radioInfo_display_bytes);
+ String packets = r.getString(R.string.radioInfo_display_packets);
+ String bytes = r.getString(R.string.radioInfo_display_bytes);
- sent.setText(txPackets + " " + packets + ", " + txBytes + " " + bytes);
- received.setText(rxPackets + " " + packets + ", " + rxBytes + " " + bytes);
- } catch (RemoteException e) {
- }
+ sent.setText(txPackets + " " + packets + ", " + txBytes + " " + bytes);
+ received.setText(rxPackets + " " + packets + ", " + rxBytes + " " + bytes);
}
/**
diff --git a/src/com/android/settings/RingerVolumePreference.java b/src/com/android/settings/RingerVolumePreference.java
index b546265..a626903 100644
--- a/src/com/android/settings/RingerVolumePreference.java
+++ b/src/com/android/settings/RingerVolumePreference.java
@@ -28,6 +28,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
+import android.media.AudioSystem;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -36,6 +37,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.preference.VolumePreference;
import android.provider.Settings;
+import android.provider.Settings.System;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -51,49 +53,46 @@ import android.widget.TextView;
* Special preference type that allows configuration of both the ring volume and
* notification volume.
*/
-public class RingerVolumePreference extends VolumePreference implements
- CheckBox.OnCheckedChangeListener, OnClickListener {
+public class RingerVolumePreference extends VolumePreference implements OnClickListener {
private static final String TAG = "RingerVolumePreference";
private static final int MSG_RINGER_MODE_CHANGED = 101;
- private CheckBox mNotificationsUseRingVolumeCheckbox;
private SeekBarVolumizer [] mSeekBarVolumizer;
private boolean mIgnoreVolumeKeys;
// These arrays must all match in length and order
private static final int[] SEEKBAR_ID = new int[] {
- R.id.notification_volume_seekbar,
R.id.media_volume_seekbar,
+ R.id.ringer_volume_seekbar,
+ R.id.notification_volume_seekbar,
R.id.alarm_volume_seekbar
};
- private static final int[] NEED_VOICE_CAPABILITY_ID = new int[] {
- R.id.ringtone_label,
- com.android.internal.R.id.seekbar,
- R.id.same_notification_volume
- };
-
private static final int[] SEEKBAR_TYPE = new int[] {
- AudioManager.STREAM_NOTIFICATION,
AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_NOTIFICATION,
AudioManager.STREAM_ALARM
};
private static final int[] CHECKBOX_VIEW_ID = new int[] {
+ R.id.media_mute_button,
+ R.id.ringer_mute_button,
R.id.notification_mute_button,
- R.id.volume_mute_button,
R.id.alarm_mute_button
};
private static final int[] SEEKBAR_MUTED_RES_ID = new int[] {
- com.android.internal.R.drawable.ic_audio_notification_mute,
com.android.internal.R.drawable.ic_audio_vol_mute,
+ com.android.internal.R.drawable.ic_audio_ring_notif_mute,
+ com.android.internal.R.drawable.ic_audio_notification_mute,
com.android.internal.R.drawable.ic_audio_alarm_mute
};
private static final int[] SEEKBAR_UNMUTED_RES_ID = new int[] {
- com.android.internal.R.drawable.ic_audio_notification,
com.android.internal.R.drawable.ic_audio_vol,
+ com.android.internal.R.drawable.ic_audio_ring_notif,
+ com.android.internal.R.drawable.ic_audio_notification,
com.android.internal.R.drawable.ic_audio_alarm
};
@@ -167,22 +166,15 @@ public class RingerVolumePreference extends VolumePreference implements
}
}
- //mNotificationVolumeTitle = (TextView) view.findViewById(R.id.notification_volume_title);
- mNotificationsUseRingVolumeCheckbox =
- (CheckBox) view.findViewById(R.id.same_notification_volume);
- mNotificationsUseRingVolumeCheckbox.setOnCheckedChangeListener(this);
- mNotificationsUseRingVolumeCheckbox.setChecked(
- Utils.isVoiceCapable(getContext())
- && Settings.System.getInt(
- getContext().getContentResolver(),
- Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1);
- setNotificationVolumeVisibility(!mNotificationsUseRingVolumeCheckbox.isChecked());
- disableSettingsThatNeedVoice(view);
-
+ final int silentableStreams = System.getInt(getContext().getContentResolver(),
+ System.MODE_RINGER_STREAMS_AFFECTED,
+ ((1 << AudioSystem.STREAM_NOTIFICATION) | (1 << AudioSystem.STREAM_RING)));
// Register callbacks for mute/unmute buttons
for (int i = 0; i < mCheckBoxes.length; i++) {
ImageView checkbox = (ImageView) view.findViewById(CHECKBOX_VIEW_ID[i]);
- checkbox.setOnClickListener(this);
+ if ((silentableStreams & (1 << SEEKBAR_TYPE[i])) != 0) {
+ checkbox.setOnClickListener(this);
+ }
mCheckBoxes[i] = checkbox;
}
@@ -197,13 +189,23 @@ public class RingerVolumePreference extends VolumePreference implements
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED,
- intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, intent
+ .getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
}
}
};
getContext().registerReceiver(mRingModeChangedReceiver, filter);
}
+
+ // Disable either ringer+notifications or notifications
+ int id;
+ if (!Utils.isVoiceCapable(getContext())) {
+ id = R.id.ringer_section;
+ } else {
+ id = R.id.notification_section;
+ }
+ View hideSection = view.findViewById(id);
+ hideSection.setVisibility(View.GONE);
}
private Uri getMediaVolumeUri(Context context) {
@@ -212,15 +214,6 @@ public class RingerVolumePreference extends VolumePreference implements
+ "/" + R.raw.media_volume);
}
- private void disableSettingsThatNeedVoice(View parent) {
- final boolean voiceCapable = Utils.isVoiceCapable(getContext());
- if (!voiceCapable) {
- for (int id : NEED_VOICE_CAPABILITY_ID) {
- parent.findViewById(id).setVisibility(View.GONE);
- }
- }
- }
-
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
@@ -236,20 +229,9 @@ public class RingerVolumePreference extends VolumePreference implements
@Override
public void onActivityStop() {
super.onActivityStop();
- cleanup();
- }
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- setNotificationVolumeVisibility(!isChecked);
-
- Settings.System.putInt(getContext().getContentResolver(),
- Settings.System.NOTIFICATIONS_USE_RING_VOLUME, isChecked ? 1 : 0);
-
- if (isChecked) {
- // The user wants the notification to be same as ring, so do a
- // one-time sync right now
- mAudioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION,
- mAudioManager.getStreamVolume(AudioManager.STREAM_RING), 0);
+ for (SeekBarVolumizer vol : mSeekBarVolumizer) {
+ if (vol != null) vol.stopSample();
}
}
@@ -278,14 +260,6 @@ public class RingerVolumePreference extends VolumePreference implements
}
}
- private void setNotificationVolumeVisibility(boolean visible) {
- if (mSeekBarVolumizer[0] != null) {
- mSeekBarVolumizer[0].getSeekBar().setVisibility(
- visible ? View.VISIBLE : View.GONE);
- }
- // mNotificationVolumeTitle.setVisibility(visible ? View.VISIBLE : View.GONE);
- }
-
private void cleanup() {
for (int i = 0; i < SEEKBAR_ID.length; i++) {
if (mSeekBarVolumizer[i] != null) {
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index dc4c42b..9a83311 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -19,15 +19,11 @@ package com.android.settings;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
-import com.android.internal.widget.LockPatternUtils;
-
+import android.app.AlertDialog;
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.database.Cursor;
-import android.location.LocationManager;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.CheckBoxPreference;
@@ -41,15 +37,15 @@ import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.util.Log;
+import com.android.internal.widget.LockPatternUtils;
+
import java.util.ArrayList;
-import java.util.Observable;
-import java.util.Observer;
/**
* Gesture lock pattern settings.
*/
public class SecuritySettings extends SettingsPreferenceFragment
- implements OnPreferenceChangeListener {
+ implements OnPreferenceChangeListener, DialogInterface.OnClickListener {
// Lock Settings
private static final String KEY_UNLOCK_SET_OR_CHANGE = "unlock_set_or_change";
@@ -60,47 +56,28 @@ public class SecuritySettings extends SettingsPreferenceFragment
private static final String KEY_LOCK_AFTER_TIMEOUT = "lock_after_timeout";
private static final int SET_OR_CHANGE_LOCK_METHOD_REQUEST = 123;
- // Location Settings
- private static final String KEY_LOCATION_CATEGORY = "location_category";
- private static final String KEY_LOCATION_NETWORK = "location_network";
- private static final String KEY_LOCATION_GPS = "location_gps";
- private static final String KEY_ASSISTED_GPS = "assisted_gps";
- private static final String KEY_USE_LOCATION = "location_use_for_services";
-
// Misc Settings
private static final String KEY_SIM_LOCK = "sim_lock";
private static final String KEY_SHOW_PASSWORD = "show_password";
- private static final String KEY_ENABLE_CREDENTIALS = "enable_credentials";
private static final String KEY_RESET_CREDENTIALS = "reset_credentials";
-
- private static final String TAG = "SecuritySettings";
-
- private CheckBoxPreference mNetwork;
- private CheckBoxPreference mGps;
- private CheckBoxPreference mAssistedGps;
- private CheckBoxPreference mUseLocation;
+ private static final String KEY_TOGGLE_INSTALL_APPLICATIONS = "toggle_install_applications";
DevicePolicyManager mDPM;
- // These provide support for receiving notification when Location Manager settings change.
- // This is necessary because the Network Location Provider can change settings
- // if the user does not confirm enabling the provider.
- private ContentQueryMap mContentQueryMap;
-
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private LockPatternUtils mLockPatternUtils;
private ListPreference mLockAfter;
- private Observer mSettingsObserver;
-
private CheckBoxPreference mVisiblePattern;
private CheckBoxPreference mTactileFeedback;
private CheckBoxPreference mShowPassword;
- private CheckBoxPreference mEnableCredentials;
private Preference mResetCredentials;
+ private CheckBoxPreference mToggleAppInstallation;
+ private DialogInterface mWarnInstallApps;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -112,25 +89,6 @@ public class SecuritySettings extends SettingsPreferenceFragment
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
}
- @Override
- public void onStart() {
- super.onStart();
- // listen for Location Manager settings changes
- Cursor settingsCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, null,
- "(" + Settings.System.NAME + "=?)",
- new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED},
- null);
- mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (mSettingsObserver != null) {
- mContentQueryMap.deleteObserver(mSettingsObserver);
- }
- }
-
private PreferenceScreen createPreferenceHierarchy() {
PreferenceScreen root = getPreferenceScreen();
if (root != null) {
@@ -139,32 +97,6 @@ public class SecuritySettings extends SettingsPreferenceFragment
addPreferencesFromResource(R.xml.security_settings);
root = getPreferenceScreen();
- mNetwork = (CheckBoxPreference) root.findPreference(KEY_LOCATION_NETWORK);
- mGps = (CheckBoxPreference) root.findPreference(KEY_LOCATION_GPS);
- mAssistedGps = (CheckBoxPreference) root.findPreference(KEY_ASSISTED_GPS);
- if (GoogleLocationSettingHelper.isAvailable(getActivity())) {
- // GSF present, Add setting for 'Use My Location'
- PreferenceGroup locationCat =
- (PreferenceGroup) root.findPreference(KEY_LOCATION_CATEGORY);
- CheckBoxPreference useLocation = new CheckBoxPreference(getActivity());
- useLocation.setKey(KEY_USE_LOCATION);
- useLocation.setTitle(R.string.use_location_title);
- useLocation.setSummaryOn(R.string.use_location_summary_enabled);
- useLocation.setSummaryOff(R.string.use_location_summary_disabled);
- useLocation.setChecked(
- GoogleLocationSettingHelper.getUseLocationForServices(getActivity())
- == GoogleLocationSettingHelper.USE_LOCATION_FOR_SERVICES_ON);
- useLocation.setPersistent(false);
- useLocation.setOnPreferenceChangeListener(this);
- locationCat.addPreference(useLocation);
- mUseLocation = useLocation;
- }
-
- // Change the summary for wifi-only devices
- if (Utils.isWifiOnly()) {
- mNetwork.setSummaryOn(R.string.location_neighborhood_level_wifi);
- }
-
// Add options for lock/unlock screen
int resid = 0;
if (!mLockPatternUtils.isSecure()) {
@@ -239,13 +171,52 @@ public class SecuritySettings extends SettingsPreferenceFragment
mShowPassword = (CheckBoxPreference) root.findPreference(KEY_SHOW_PASSWORD);
// Credential storage
- mEnableCredentials = (CheckBoxPreference) root.findPreference(KEY_ENABLE_CREDENTIALS);
- mEnableCredentials.setOnPreferenceChangeListener(this);
mResetCredentials = root.findPreference(KEY_RESET_CREDENTIALS);
+ mToggleAppInstallation = (CheckBoxPreference) findPreference(
+ KEY_TOGGLE_INSTALL_APPLICATIONS);
+ mToggleAppInstallation.setChecked(isNonMarketAppsAllowed());
+
return root;
}
+ private boolean isNonMarketAppsAllowed() {
+ return Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
+ }
+
+ private void setNonMarketAppsAllowed(boolean enabled) {
+ // Change the system setting
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS,
+ enabled ? 1 : 0);
+ }
+
+ private void warnAppInstallation() {
+ // TODO: DialogFragment?
+ mWarnInstallApps = new AlertDialog.Builder(getActivity()).setTitle(
+ getResources().getString(R.string.error_title))
+ .setIcon(com.android.internal.R.drawable.ic_dialog_alert)
+ .setMessage(getResources().getString(R.string.install_all_warning))
+ .setPositiveButton(android.R.string.yes, this)
+ .setNegativeButton(android.R.string.no, null)
+ .show();
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (dialog == mWarnInstallApps && which == DialogInterface.BUTTON_POSITIVE) {
+ setNonMarketAppsAllowed(true);
+ mToggleAppInstallation.setChecked(true);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mWarnInstallApps != null) {
+ mWarnInstallApps.dismiss();
+ }
+ }
+
private void setupLockAfterPreference() {
// Compatible with pre-Froyo
long currentTimeout = Settings.Secure.getLong(getContentResolver(),
@@ -266,7 +237,7 @@ public class SecuritySettings extends SettingsPreferenceFragment
private void updateLockAfterPreferenceSummary() {
// Update summary message with current value
long currentTimeout = Settings.Secure.getLong(getContentResolver(),
- Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 0);
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, 5000);
final CharSequence[] entries = mLockAfter.getEntries();
final CharSequence[] values = mLockAfter.getEntryValues();
int best = 0;
@@ -315,16 +286,6 @@ public class SecuritySettings extends SettingsPreferenceFragment
// Make sure we reload the preference hierarchy since some of these settings
// depend on others...
createPreferenceHierarchy();
- updateLocationToggles();
-
- if (mSettingsObserver == null) {
- mSettingsObserver = new Observer() {
- public void update(Observable o, Object arg) {
- updateLocationToggles();
- }
- };
- mContentQueryMap.addObserver(mSettingsObserver);
- }
final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils();
if (mVisiblePattern != null) {
@@ -337,15 +298,12 @@ public class SecuritySettings extends SettingsPreferenceFragment
mShowPassword.setChecked(Settings.System.getInt(getContentResolver(),
Settings.System.TEXT_SHOW_PASSWORD, 1) != 0);
- int state = KeyStore.getInstance().test();
- mEnableCredentials.setChecked(state == KeyStore.NO_ERROR);
- mEnableCredentials.setEnabled(state != KeyStore.UNINITIALIZED);
- mResetCredentials.setEnabled(state != KeyStore.UNINITIALIZED);
+ KeyStore.State state = KeyStore.getInstance().state();
+ mResetCredentials.setEnabled(state != KeyStore.State.UNINITIALIZED);
}
@Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
final String key = preference.getKey();
final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils();
@@ -361,19 +319,13 @@ public class SecuritySettings extends SettingsPreferenceFragment
} else if (preference == mShowPassword) {
Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,
mShowPassword.isChecked() ? 1 : 0);
- } else if (preference == mNetwork) {
- Settings.Secure.setLocationProviderEnabled(getContentResolver(),
- LocationManager.NETWORK_PROVIDER, mNetwork.isChecked());
- } else if (preference == mGps) {
- boolean enabled = mGps.isChecked();
- Settings.Secure.setLocationProviderEnabled(getContentResolver(),
- LocationManager.GPS_PROVIDER, enabled);
- if (mAssistedGps != null) {
- mAssistedGps.setEnabled(enabled);
+ } else if (preference == mToggleAppInstallation) {
+ if (mToggleAppInstallation.isChecked()) {
+ mToggleAppInstallation.setChecked(false);
+ warnAppInstallation();
+ } else {
+ setNonMarketAppsAllowed(false);
}
- } else if (preference == mAssistedGps) {
- Settings.Secure.putInt(getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED,
- mAssistedGps.isChecked() ? 1 : 0);
} else {
// If we didn't handle it, let preferences handle it.
return super.onPreferenceTreeClick(preferenceScreen, preference);
@@ -382,29 +334,12 @@ public class SecuritySettings extends SettingsPreferenceFragment
return true;
}
- /*
- * Creates toggles for each available location provider
- */
- private void updateLocationToggles() {
- ContentResolver res = getContentResolver();
- boolean gpsEnabled = Settings.Secure.isLocationProviderEnabled(
- res, LocationManager.GPS_PROVIDER);
- mNetwork.setChecked(Settings.Secure.isLocationProviderEnabled(
- res, LocationManager.NETWORK_PROVIDER));
- mGps.setChecked(gpsEnabled);
- if (mAssistedGps != null) {
- mAssistedGps.setChecked(Settings.Secure.getInt(res,
- Settings.Secure.ASSISTED_GPS_ENABLED, 2) == 1);
- mAssistedGps.setEnabled(gpsEnabled);
- }
- }
-
private boolean isToggled(Preference pref) {
return ((CheckBoxPreference) pref).isChecked();
}
/**
- * @see #confirmPatternThenDisableAndClear
+ * see confirmPatternThenDisableAndClear
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
@@ -422,21 +357,6 @@ public class SecuritySettings extends SettingsPreferenceFragment
Log.e("SecuritySettings", "could not persist lockAfter timeout setting", e);
}
updateLockAfterPreferenceSummary();
- } else if (preference == mUseLocation) {
- boolean newValue = (value == null ? false : (Boolean) value);
- GoogleLocationSettingHelper.setUseLocationForServices(getActivity(), newValue);
- // We don't want to change the value immediately here, since the user may click
- // disagree in the dialog that pops up. When the activity we just launched exits, this
- // activity will be restated and the new value re-read, so the checkbox will get its
- // new value then.
- return false;
- } else if (preference == mEnableCredentials) {
- if (value != null && (Boolean) value) {
- getActivity().startActivity(new Intent(CredentialStorage.ACTION_UNLOCK));
- return false;
- } else {
- KeyStore.getInstance().lock();
- }
}
return true;
}
diff --git a/src/com/android/settings/SetFullBackupPassword.java b/src/com/android/settings/SetFullBackupPassword.java
new file mode 100644
index 0000000..9f3f29f
--- /dev/null
+++ b/src/com/android/settings/SetFullBackupPassword.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.backup.IBackupManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class SetFullBackupPassword extends Activity {
+ static final String TAG = "SetFullBackupPassword";
+
+ IBackupManager mBackupManager;
+ TextView mCurrentPw, mNewPw, mConfirmNewPw;
+ Button mCancel, mSet;
+
+ OnClickListener mButtonListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v == mSet) {
+ final String curPw = mCurrentPw.getText().toString();
+ final String newPw = mNewPw.getText().toString();
+ final String confirmPw = mConfirmNewPw.getText().toString();
+
+ if (!newPw.equals(confirmPw)) {
+ // Mismatch between new pw and its confirmation re-entry
+Log.i(TAG, "password mismatch");
+ Toast.makeText(SetFullBackupPassword.this,
+ "!!! New password and confirmation don't match !!!",
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ // TODO: should we distinguish cases of has/hasn't set a pw before?
+
+ if (setBackupPassword(curPw, newPw)) {
+ // success
+Log.i(TAG, "password set successfully");
+ Toast.makeText(SetFullBackupPassword.this,
+ "!!! New backup password set !!!",
+ Toast.LENGTH_LONG).show();
+ finish();
+ } else {
+ // failure -- bad existing pw, usually
+Log.i(TAG, "failure; password mismatch?");
+ Toast.makeText(SetFullBackupPassword.this,
+ "!!! Failure setting backup password !!!",
+ Toast.LENGTH_LONG).show();
+ }
+ } else if (v == mCancel) {
+ finish();
+ } else {
+ Log.w(TAG, "Click on unknown view");
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
+
+ setContentView(R.layout.set_backup_pw);
+
+ mCurrentPw = (TextView) findViewById(R.id.current_backup_pw);
+ mNewPw = (TextView) findViewById(R.id.new_backup_pw);
+ mConfirmNewPw = (TextView) findViewById(R.id.confirm_new_backup_pw);
+
+ mCancel = (Button) findViewById(R.id.backup_pw_cancel_button);
+ mSet = (Button) findViewById(R.id.backup_pw_set_button);
+
+ mCancel.setOnClickListener(mButtonListener);
+ mSet.setOnClickListener(mButtonListener);
+ }
+
+ private boolean setBackupPassword(String currentPw, String newPw) {
+ try {
+ return mBackupManager.setBackupPassword(currentPw, newPw);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to communicate with backup manager");
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 2532747..272c0d1 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -16,18 +16,35 @@
package com.android.settings;
+import com.android.settings.accounts.AccountSyncSettings;
+import com.android.settings.bluetooth.BluetoothEnabler;
+import com.android.settings.fuelgauge.PowerUsageSummary;
+import com.android.settings.wifi.WifiEnabler;
+
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
+import android.preference.Preference;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+import android.text.TextUtils;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.Switch;
+import android.widget.TextView;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -36,15 +53,18 @@ import java.util.List;
*/
public class Settings extends PreferenceActivity implements ButtonBarHandler {
+ private static final String LOG_TAG = "Settings";
private static final String META_DATA_KEY_HEADER_ID =
- "com.android.settings.TOP_LEVEL_HEADER_ID";
+ "com.android.settings.TOP_LEVEL_HEADER_ID";
private static final String META_DATA_KEY_FRAGMENT_CLASS =
- "com.android.settings.FRAGMENT_CLASS";
+ "com.android.settings.FRAGMENT_CLASS";
private static final String META_DATA_KEY_PARENT_TITLE =
"com.android.settings.PARENT_FRAGMENT_TITLE";
private static final String META_DATA_KEY_PARENT_FRAGMENT_CLASS =
"com.android.settings.PARENT_FRAGMENT_CLASS";
+ private static final String EXTRA_THEME = "settings:theme";
+
private static final String SAVE_KEY_CURRENT_HEADER = "com.android.settings.CURRENT_HEADER";
private static final String SAVE_KEY_PARENT_HEADER = "com.android.settings.PARENT_HEADER";
@@ -58,9 +78,14 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
// TODO: Update Call Settings based on airplane mode state.
protected HashMap<Integer, Integer> mHeaderIndexMap = new HashMap<Integer, Integer>();
+ private List<Header> mHeaders;
@Override
protected void onCreate(Bundle savedInstanceState) {
+ final int theme = getIntent().getIntExtra(
+ EXTRA_THEME, android.R.style.Theme_Holo);
+ setTheme(theme);
+
getMetaData();
mInLocalHeaderSwitch = true;
super.onCreate(savedInstanceState);
@@ -92,6 +117,10 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
}
});
}
+
+ // TODO Add support for android.R.id.home in all Setting's onOptionsItemSelected
+ // getActionBar().setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP,
+ // ActionBar.DISPLAY_HOME_AS_UP);
}
@Override
@@ -107,6 +136,26 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
}
}
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ ListAdapter listAdapter = getListAdapter();
+ if (listAdapter instanceof HeaderAdapter) {
+ ((HeaderAdapter) listAdapter).resume();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ ListAdapter listAdapter = getListAdapter();
+ if (listAdapter instanceof HeaderAdapter) {
+ ((HeaderAdapter) listAdapter).pause();
+ }
+ }
+
private void switchToHeaderLocal(Header header) {
mInLocalHeaderSwitch = true;
switchToHeader(header);
@@ -148,7 +197,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
mParentHeader.title = parentInfo.metaData.getString(META_DATA_KEY_PARENT_TITLE);
}
} catch (NameNotFoundException nnfe) {
- Log.w("Settings", "Could not find parent activity : " + className);
+ Log.w(LOG_TAG, "Could not find parent activity : " + className);
}
}
@@ -158,7 +207,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
// If it is not launched from history, then reset to top-level
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0
- && mFirstHeader != null) {
+ && mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {
switchToHeaderLocal(mFirstHeader);
}
}
@@ -174,21 +223,24 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
@Override
public Intent getIntent() {
- String startingFragment = getStartingFragmentClass(super.getIntent());
+ Intent superIntent = super.getIntent();
+ String startingFragment = getStartingFragmentClass(superIntent);
+ // This is called from super.onCreate, isMultiPane() is not yet reliable
+ // Do not use onIsHidingHeaders either, which relies itself on this method
if (startingFragment != null && !onIsMultiPane()) {
- Intent modIntent = new Intent(super.getIntent());
+ Intent modIntent = new Intent(superIntent);
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
- Bundle args = super.getIntent().getExtras();
+ Bundle args = superIntent.getExtras();
if (args != null) {
args = new Bundle(args);
} else {
args = new Bundle();
}
- args.putParcelable("intent", super.getIntent());
- modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, super.getIntent().getExtras());
+ args.putParcelable("intent", superIntent);
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, superIntent.getExtras());
return modIntent;
}
- return super.getIntent();
+ return superIntent;
}
/**
@@ -204,7 +256,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
if ("com.android.settings.ManageApplications".equals(intentClass)
|| "com.android.settings.RunningServices".equals(intentClass)
|| "com.android.settings.applications.StorageUse".equals(intentClass)) {
- // Old name of manage apps.
+ // Old names of manage apps.
intentClass = com.android.settings.applications.ManageApplications.class.getName();
}
@@ -226,17 +278,38 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
mCurrentHeader = header;
return header;
}
- return super.onGetInitialHeader();
+
+ return mFirstHeader;
}
+ @Override
+ public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
+ int titleRes, int shortTitleRes) {
+ Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,
+ titleRes, shortTitleRes);
+
+ // some fragments would like a custom activity theme
+ if (DataUsageSummary.class.getName().equals(fragmentName) ||
+ PowerUsageSummary.class.getName().equals(fragmentName) ||
+ AccountSyncSettings.class.getName().equals(fragmentName) ||
+ UserDictionarySettings.class.getName().equals(fragmentName)) {
+ intent.putExtra(EXTRA_THEME, android.R.style.Theme_Holo);
+ }
+
+ intent.setClass(this, SubSettings.class);
+ return intent;
+ }
+
/**
* Populate the activity with the top-level headers.
*/
@Override
- public void onBuildHeaders(List<Header> target) {
- loadHeadersFromResource(R.xml.settings_headers, target);
+ public void onBuildHeaders(List<Header> headers) {
+ loadHeadersFromResource(R.xml.settings_headers, headers);
+
+ updateHeaderList(headers);
- updateHeaderList(target);
+ mHeaders = headers;
}
private void updateHeaderList(List<Header> target) {
@@ -250,14 +323,25 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
target.remove(header);
} else if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
Utils.updateHeaderToSpecificActivityFromMetaDataOrRemove(this, target, header);
- } else if (id == R.id.call_settings) {
- if (!Utils.isVoiceCapable(this))
+ } else if (id == R.id.wifi_settings) {
+ // Remove WiFi Settings if WiFi service is not available.
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
target.remove(header);
+ }
+ } else if (id == R.id.bluetooth_settings) {
+ // Remove Bluetooth Settings if Bluetooth service is not available.
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
+ target.remove(header);
+ }
}
+
// Increment if the current one wasn't removed by the Utils code.
if (target.get(i) == header) {
// Hold on to the first header, when we need to reset to the top-level
- if (i == 0) mFirstHeader = header;
+ if (mFirstHeader == null &&
+ HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
+ mFirstHeader = header;
+ }
mHeaderIndexMap.put(id, i);
i++;
}
@@ -287,6 +371,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
}
}
} catch (NameNotFoundException nnfe) {
+ // No recovery
}
}
@@ -300,38 +385,220 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
return super.getNextButton();
}
+ private static class HeaderAdapter extends ArrayAdapter<Header> {
+ static final int HEADER_TYPE_CATEGORY = 0;
+ static final int HEADER_TYPE_NORMAL = 1;
+ static final int HEADER_TYPE_SWITCH = 2;
+ private static final int HEADER_TYPE_COUNT = HEADER_TYPE_SWITCH + 1;
+
+ private final WifiEnabler mWifiEnabler;
+ private final BluetoothEnabler mBluetoothEnabler;
+
+ private static class HeaderViewHolder {
+ ImageView icon;
+ TextView title;
+ TextView summary;
+ Switch switch_;
+ }
+
+ private LayoutInflater mInflater;
+
+ static int getHeaderType(Header header) {
+ if (header.fragment == null && header.intent == null) {
+ return HEADER_TYPE_CATEGORY;
+ } else if (header.id == R.id.wifi_settings || header.id == R.id.bluetooth_settings) {
+ return HEADER_TYPE_SWITCH;
+ } else {
+ return HEADER_TYPE_NORMAL;
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ Header header = getItem(position);
+ return getHeaderType(header);
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false; // because of categories
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItemViewType(position) != HEADER_TYPE_CATEGORY;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return HEADER_TYPE_COUNT;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ public HeaderAdapter(Context context, List<Header> objects) {
+ super(context, 0, objects);
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ // Temp Switches provided as placeholder until the adapter replaces these with actual
+ // Switches inflated from their layouts. Must be done before adapter is set in super
+ mWifiEnabler = new WifiEnabler(context, new Switch(context));
+ mBluetoothEnabler = new BluetoothEnabler(context, new Switch(context));
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ HeaderViewHolder holder;
+ Header header = getItem(position);
+ int headerType = getHeaderType(header);
+ View view = null;
+
+ if (convertView == null) {
+ holder = new HeaderViewHolder();
+ switch (headerType) {
+ case HEADER_TYPE_CATEGORY:
+ view = new TextView(getContext(), null,
+ android.R.attr.listSeparatorTextViewStyle);
+ holder.title = (TextView) view;
+ break;
+
+ case HEADER_TYPE_SWITCH:
+ view = mInflater.inflate(R.layout.preference_header_switch_item, parent,
+ false);
+ holder.icon = (ImageView) view.findViewById(R.id.icon);
+ holder.title = (TextView)
+ view.findViewById(com.android.internal.R.id.title);
+ holder.summary = (TextView)
+ view.findViewById(com.android.internal.R.id.summary);
+ holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
+ break;
+
+ case HEADER_TYPE_NORMAL:
+ view = mInflater.inflate(
+ com.android.internal.R.layout.preference_header_item, parent,
+ false);
+ holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
+ holder.title = (TextView)
+ view.findViewById(com.android.internal.R.id.title);
+ holder.summary = (TextView)
+ view.findViewById(com.android.internal.R.id.summary);
+ break;
+ }
+ view.setTag(holder);
+ } else {
+ view = convertView;
+ holder = (HeaderViewHolder) view.getTag();
+ }
+
+ // All view fields must be updated every time, because the view may be recycled
+ switch (headerType) {
+ case HEADER_TYPE_CATEGORY:
+ holder.title.setText(header.getTitle(getContext().getResources()));
+ break;
+
+ case HEADER_TYPE_SWITCH:
+ // Would need a different treatment if the main menu had more switches
+ if (header.id == R.id.wifi_settings) {
+ mWifiEnabler.setSwitch(holder.switch_);
+ } else {
+ mBluetoothEnabler.setSwitch(holder.switch_);
+ }
+ // No break, fall through on purpose to update common fields
+
+ //$FALL-THROUGH$
+ case HEADER_TYPE_NORMAL:
+ holder.icon.setImageResource(header.iconRes);
+ holder.title.setText(header.getTitle(getContext().getResources()));
+ CharSequence summary = header.getSummary(getContext().getResources());
+ if (!TextUtils.isEmpty(summary)) {
+ holder.summary.setVisibility(View.VISIBLE);
+ holder.summary.setText(summary);
+ } else {
+ holder.summary.setVisibility(View.GONE);
+ }
+ break;
+ }
+
+ return view;
+ }
+
+ public void resume() {
+ mWifiEnabler.resume();
+ mBluetoothEnabler.resume();
+ }
+
+ public void pause() {
+ mWifiEnabler.pause();
+ mBluetoothEnabler.pause();
+ }
+ }
+
+ @Override
+ public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
+ // Override the fragment title for Wallpaper settings
+ CharSequence title = pref.getTitle();
+ if (pref.getFragment().equals(WallpaperTypeSettings.class.getName())) {
+ title = getString(R.string.wallpaper_settings_fragment_title);
+ }
+ startPreferencePanel(pref.getFragment(), pref.getExtras(), 0, title, null, 0);
+ return true;
+ }
+
+ @Override
+ public void setListAdapter(ListAdapter adapter) {
+ if (mHeaders == null) {
+ mHeaders = new ArrayList<Header>();
+ // When the saved state provides the list of headers, onBuildHeaders is not called
+ // Copy the list of Headers from the adapter, preserving their order
+ for (int i = 0; i < adapter.getCount(); i++) {
+ mHeaders.add((Header) adapter.getItem(i));
+ }
+ }
+
+ // Ignore the adapter provided by PreferenceActivity and substitute ours instead
+ super.setListAdapter(new HeaderAdapter(this, mHeaders));
+ }
+
/*
* Settings subclasses for launching independently.
*/
-
- public static class BluetoothSettingsActivity extends Settings { }
- public static class WirelessSettingsActivity extends Settings { }
- public static class TetherSettingsActivity extends Settings { }
- public static class VpnSettingsActivity extends Settings { }
- public static class DateTimeSettingsActivity extends Settings { }
- public static class StorageSettingsActivity extends Settings { }
- public static class WifiSettingsActivity extends Settings { }
- public static class InputMethodAndLanguageSettingsActivity extends Settings { }
- public static class InputMethodConfigActivity extends Settings { }
- public static class InputMethodAndSubtypeEnablerActivity extends Settings { }
- public static class LocalePickerActivity extends Settings { }
- public static class UserDictionarySettingsActivity extends Settings { }
- public static class SoundSettingsActivity extends Settings { }
- public static class DisplaySettingsActivity extends Settings { }
- public static class DeviceInfoSettingsActivity extends Settings { }
- public static class ApplicationSettingsActivity extends Settings { }
- public static class ManageApplicationsActivity extends Settings { }
- public static class StorageUseActivity extends Settings { }
- public static class DevelopmentSettingsActivity extends Settings { }
- public static class AccessibilitySettingsActivity extends Settings { }
- public static class SecuritySettingsActivity extends Settings { }
- public static class PrivacySettingsActivity extends Settings { }
- public static class DockSettingsActivity extends Settings { }
- public static class RunningServicesActivity extends Settings { }
- public static class ManageAccountsSettingsActivity extends Settings { }
- public static class PowerUsageSummaryActivity extends Settings { }
- public static class AccountSyncSettingsActivity extends Settings { }
- public static class AccountSyncSettingsInAddAccountActivity extends Settings { }
- public static class CryptKeeperSettingsActivity extends Settings { }
- public static class DeviceAdminSettingsActivity extends Settings { }
+ public static class BluetoothSettingsActivity extends Settings { /* empty */ }
+ public static class WirelessSettingsActivity extends Settings { /* empty */ }
+ public static class TetherSettingsActivity extends Settings { /* empty */ }
+ public static class VpnSettingsActivity extends Settings { /* empty */ }
+ public static class DateTimeSettingsActivity extends Settings { /* empty */ }
+ public static class StorageSettingsActivity extends Settings { /* empty */ }
+ public static class WifiSettingsActivity extends Settings { /* empty */ }
+ public static class WifiP2pSettingsActivity extends Settings { /* empty */ }
+ public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ }
+ public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ }
+ public static class SpellCheckersSettingsActivity extends Settings { /* empty */ }
+ public static class LocalePickerActivity extends Settings { /* empty */ }
+ public static class UserDictionarySettingsActivity extends Settings { /* empty */ }
+ public static class SoundSettingsActivity extends Settings { /* empty */ }
+ public static class DisplaySettingsActivity extends Settings { /* empty */ }
+ public static class DeviceInfoSettingsActivity extends Settings { /* empty */ }
+ public static class ApplicationSettingsActivity extends Settings { /* empty */ }
+ public static class ManageApplicationsActivity extends Settings { /* empty */ }
+ public static class StorageUseActivity extends Settings { /* empty */ }
+ public static class DevelopmentSettingsActivity extends Settings { /* empty */ }
+ public static class AccessibilitySettingsActivity extends Settings { /* empty */ }
+ public static class SecuritySettingsActivity extends Settings { /* empty */ }
+ public static class LocationSettingsActivity extends Settings { /* empty */ }
+ public static class PrivacySettingsActivity extends Settings { /* empty */ }
+ public static class DockSettingsActivity extends Settings { /* empty */ }
+ public static class RunningServicesActivity extends Settings { /* empty */ }
+ public static class ManageAccountsSettingsActivity extends Settings { /* empty */ }
+ public static class PowerUsageSummaryActivity extends Settings { /* empty */ }
+ public static class AccountSyncSettingsActivity extends Settings { /* empty */ }
+ public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ }
+ public static class CryptKeeperSettingsActivity extends Settings { /* empty */ }
+ public static class DeviceAdminSettingsActivity extends Settings { /* empty */ }
+ public static class DataUsageSummaryActivity extends Settings { /* empty */ }
+ public static class AdvancedWifiSettingsActivity extends Settings { /* empty */ }
+ public static class TextToSpeechSettingsActivity extends Settings { /* empty */ }
+ public static class NfcSharingSettingsActivity extends Settings { /* empty */ }
}
diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java
index 99828ce..2960180 100644
--- a/src/com/android/settings/SettingsLicenseActivity.java
+++ b/src/com/android/settings/SettingsLicenseActivity.java
@@ -21,7 +21,6 @@ import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -46,7 +45,7 @@ import java.util.zip.GZIPInputStream;
public class SettingsLicenseActivity extends Activity {
private static final String TAG = "SettingsLicenseActivity";
- private static final boolean LOGV = false || Config.LOGV;
+ private static final boolean LOGV = false || false;
private static final String DEFAULT_LICENSE_PATH = "/system/etc/NOTICE.html.gz";
private static final String PROPERTY_LICENSE_PATH = "ro.config.license_path";
diff --git a/src/com/android/settings/SoundSettings.java b/src/com/android/settings/SoundSettings.java
index 4ca7d4a..d1f8247 100644
--- a/src/com/android/settings/SoundSettings.java
+++ b/src/com/android/settings/SoundSettings.java
@@ -21,22 +21,36 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.audiofx.AudioEffect;
+import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
import android.os.Vibrator;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
+import android.provider.MediaStore;
import android.provider.Settings;
+import android.provider.MediaStore.Images.Media;
import android.provider.Settings.SettingNotFoundException;
import android.telephony.TelephonyManager;
import android.util.Log;
+import java.util.List;
+
public class SoundSettings extends SettingsPreferenceFragment implements
Preference.OnPreferenceChangeListener {
- private static final String TAG = "SoundAndDisplaysSettings";
+ private static final String TAG = "SoundSettings";
/** If there is no setting in the provider, use this. */
private static final int FALLBACK_EMERGENCY_TONE_VALUE = 0;
@@ -44,6 +58,7 @@ public class SoundSettings extends SettingsPreferenceFragment implements
private static final String KEY_SILENT = "silent";
private static final String KEY_VIBRATE = "vibrate";
private static final String KEY_RING_VOLUME = "ring_volume";
+ private static final String KEY_MUSICFX = "musicfx";
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";
@@ -54,7 +69,6 @@ public class SoundSettings extends SettingsPreferenceFragment implements
private static final String KEY_RINGTONE = "ringtone";
private static final String KEY_NOTIFICATION_SOUND = "notification_sound";
private static final String KEY_CATEGORY_CALLS = "category_calls";
- private static final String KEY_CATEGORY_NOTIFICATION = "category_notification";
private static final String VALUE_VIBRATE_NEVER = "never";
private static final String VALUE_VIBRATE_ALWAYS = "always";
@@ -66,6 +80,9 @@ public class SoundSettings extends SettingsPreferenceFragment implements
KEY_EMERGENCY_TONE
};
+ private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1;
+ private static final int MSG_UPDATE_NOTIFICATION_SUMMARY = 2;
+
private CheckBoxPreference mSilent;
/*
@@ -80,7 +97,12 @@ public class SoundSettings extends SettingsPreferenceFragment implements
private CheckBoxPreference mSoundEffects;
private CheckBoxPreference mHapticFeedback;
private CheckBoxPreference mNotificationPulse;
+ private Preference mMusicFx;
private CheckBoxPreference mLockSounds;
+ private Preference mRingtonePreference;
+ private Preference mNotificationPreference;
+
+ private Runnable mRingtoneLookupRunnable;
private AudioManager mAudioManager;
@@ -93,6 +115,19 @@ public class SoundSettings extends SettingsPreferenceFragment implements
}
};
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_RINGTONE_SUMMARY:
+ mRingtonePreference.setSummary((CharSequence) msg.obj);
+ break;
+ case MSG_UPDATE_NOTIFICATION_SUMMARY:
+ mNotificationPreference.setSummary((CharSequence) msg.obj);
+ break;
+ }
+ }
+ };
+
private PreferenceGroup mSoundSettings;
@Override
@@ -126,16 +161,19 @@ public class SoundSettings extends SettingsPreferenceFragment implements
mSoundEffects = (CheckBoxPreference) findPreference(KEY_SOUND_EFFECTS);
mSoundEffects.setPersistent(false);
mSoundEffects.setChecked(Settings.System.getInt(resolver,
- Settings.System.SOUND_EFFECTS_ENABLED, 0) != 0);
+ Settings.System.SOUND_EFFECTS_ENABLED, 1) != 0);
mHapticFeedback = (CheckBoxPreference) findPreference(KEY_HAPTIC_FEEDBACK);
mHapticFeedback.setPersistent(false);
mHapticFeedback.setChecked(Settings.System.getInt(resolver,
- Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0);
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 1) != 0);
mLockSounds = (CheckBoxPreference) findPreference(KEY_LOCK_SOUNDS);
mLockSounds.setPersistent(false);
mLockSounds.setChecked(Settings.System.getInt(resolver,
Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) != 0);
+ mRingtonePreference = findPreference(KEY_RINGTONE);
+ mNotificationPreference = findPreference(KEY_NOTIFICATION_SOUND);
+
if (!((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).hasVibrator()) {
getPreferenceScreen().removePreference(mVibrate);
getPreferenceScreen().removePreference(mHapticFeedback);
@@ -165,6 +203,19 @@ public class SoundSettings extends SettingsPreferenceFragment implements
}
}
+ mMusicFx = mSoundSettings.findPreference(KEY_MUSICFX);
+ Intent i = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
+ PackageManager p = getPackageManager();
+ List<ResolveInfo> ris = p.queryIntentActivities(i, PackageManager.GET_DISABLED_COMPONENTS);
+ if (ris.size() <= 2) {
+ // no need to show the item if there is no choice for the user to make
+ // note: the built in musicfx panel has two activities (one being a
+ // compatibility shim that launches either the other activity, or a
+ // third party one), hence the check for <=2. If the implementation
+ // of the compatbility layer changes, this check may need to be updated.
+ mSoundSettings.removePreference(mMusicFx);
+ }
+
if (!Utils.isVoiceCapable(getActivity())) {
for (String prefKey : NEED_VOICE_CAPABILITY) {
Preference pref = findPreference(prefKey);
@@ -173,6 +224,19 @@ public class SoundSettings extends SettingsPreferenceFragment implements
}
}
}
+
+ mRingtoneLookupRunnable = new Runnable() {
+ public void run() {
+ if (mRingtonePreference != null) {
+ updateRingtoneName(RingtoneManager.TYPE_RINGTONE, mRingtonePreference,
+ MSG_UPDATE_RINGTONE_SUMMARY);
+ }
+ if (mNotificationPreference != null) {
+ updateRingtoneName(RingtoneManager.TYPE_NOTIFICATION, mNotificationPreference,
+ MSG_UPDATE_NOTIFICATION_SUMMARY);
+ }
+ }
+ };
}
@Override
@@ -180,6 +244,7 @@ public class SoundSettings extends SettingsPreferenceFragment implements
super.onResume();
updateState(true);
+ lookupRingtoneNames();
IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
getActivity().registerReceiver(mReceiver, filter);
@@ -286,13 +351,34 @@ public class SoundSettings extends SettingsPreferenceFragment implements
mVibrate.setValue(phoneVibrateSetting);
}
mVibrate.setSummary(mVibrate.getEntry());
+ }
+
+ private void updateRingtoneName(int type, Preference preference, int msg) {
+ if (preference == null) return;
+ Context context = getActivity();
+ if (context == null) return;
+ Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
+ CharSequence summary = context.getString(com.android.internal.R.string.ringtone_unknown);
+ // Is it a silent ringtone?
+ if (ringtoneUri == null) {
+ summary = context.getString(com.android.internal.R.string.ringtone_silent);
+ } else {
+ // Fetch the ringtone title from the media provider
+ try {
+ Cursor cursor = context.getContentResolver().query(ringtoneUri,
+ new String[] { MediaStore.Audio.Media.TITLE }, null, null, null);
+ if (cursor.moveToFirst()) {
+ summary = cursor.getString(0);
+ }
+ } catch (SQLiteException sqle) {
+ // Unknown title for the ringtone
+ }
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(msg, summary));
+ }
- int silentModeStreams = Settings.System.getInt(getContentResolver(),
- Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
- boolean isAlarmInclSilentMode = (silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0;
- mSilent.setSummary(isAlarmInclSilentMode ?
- R.string.silent_mode_incl_alarm_summary :
- R.string.silent_mode_summary);
+ private void lookupRingtoneNames() {
+ new Thread(mRingtoneLookupRunnable).start();
}
@Override
@@ -335,6 +421,9 @@ public class SoundSettings extends SettingsPreferenceFragment implements
boolean value = mNotificationPulse.isChecked();
Settings.System.putInt(getContentResolver(),
Settings.System.NOTIFICATION_LIGHT_PULSE, value ? 1 : 0);
+ } else if (preference == mMusicFx) {
+ // let the framework fire off the intent
+ return false;
}
return true;
diff --git a/src/com/android/settings/SubSettings.java b/src/com/android/settings/SubSettings.java
new file mode 100644
index 0000000..9cd3c31
--- /dev/null
+++ b/src/com/android/settings/SubSettings.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+/**
+ * Stub class for showing sub-settings; we can't use the main Settings class
+ * since for our app it is a special singleTask class.
+ */
+public class SubSettings extends Settings {
+}
diff --git a/src/com/android/settings/TestingSettingsBroadcastReceiver.java b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
index c6cd7e1..cea12c5 100644
--- a/src/com/android/settings/TestingSettingsBroadcastReceiver.java
+++ b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
@@ -6,7 +6,6 @@ import static android.provider.Telephony.Intents.SECRET_CODE_ACTION;
import android.content.Context;
import android.content.Intent;
import android.content.BroadcastReceiver;
-import android.util.Config;
import android.util.Log;
import android.view.KeyEvent;
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java
index 1513d43..f5bee3a 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/TetherSettings.java
@@ -31,6 +31,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetManager;
+import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
@@ -95,6 +96,9 @@ public class TetherSettings extends SettingsPreferenceFragment
private WifiManager mWifiManager;
private WifiConfiguration mWifiConfig = null;
+ private boolean mUsbConnected;
+ private boolean mMassStorageActive;
+
private boolean mBluetoothEnableForTether;
@Override
@@ -253,8 +257,14 @@ public class TetherSettings extends SettingsPreferenceFragment
updateState(available.toArray(new String[available.size()]),
active.toArray(new String[active.size()]),
errored.toArray(new String[errored.size()]));
- } else if (action.equals(Intent.ACTION_MEDIA_SHARED) ||
- action.equals(Intent.ACTION_MEDIA_UNSHARED)) {
+ } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
+ mMassStorageActive = true;
+ updateState();
+ } else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) {
+ mMassStorageActive = false;
+ updateState();
+ } else if (action.equals(UsbManager.ACTION_USB_STATE)) {
+ mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
updateState();
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
if (mBluetoothEnableForTether) {
@@ -285,11 +295,16 @@ public class TetherSettings extends SettingsPreferenceFragment
final Activity activity = getActivity();
+ mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState());
mTetherChangeReceiver = new TetherChangeReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
Intent intent = activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_STATE);
+ activity.registerReceiver(mTetherChangeReceiver, filter);
+
+ filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
filter.addDataScheme("file");
@@ -334,14 +349,11 @@ public class TetherSettings extends SettingsPreferenceFragment
String[] errored) {
ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
- boolean usbAvailable = false;
+ boolean usbAvailable = mUsbConnected && !mMassStorageActive;
int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
- boolean massStorageActive =
- Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState());
for (String s : available) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) {
- usbAvailable = true;
if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
usbError = cm.getLastTetherError(s);
}
@@ -377,7 +389,7 @@ public class TetherSettings extends SettingsPreferenceFragment
mUsbTether.setSummary(R.string.usb_tethering_errored_subtext);
mUsbTether.setEnabled(false);
mUsbTether.setChecked(false);
- } else if (massStorageActive) {
+ } else if (mMassStorageActive) {
mUsbTether.setSummary(R.string.usb_tethering_storage_active_subtext);
mUsbTether.setEnabled(false);
mUsbTether.setChecked(false);
@@ -434,40 +446,18 @@ public class TetherSettings extends SettingsPreferenceFragment
@Override
public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
+ ConnectivityManager cm =
+ (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+
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("");
+ if (cm.setUsbTethering(newState) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ mUsbTether.setChecked(false);
+ mUsbTether.setSummary(R.string.usb_tethering_errored_subtext);
+ return true;
}
+ mUsbTether.setSummary("");
} else if (preference == mBluetoothTether) {
boolean bluetoothTetherState = mBluetoothTether.isChecked();
@@ -486,8 +476,6 @@ public class TetherSettings extends SettingsPreferenceFragment
} else {
boolean errored = false;
- ConnectivityManager cm =
- (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
String [] tethered = cm.getTetheredIfaces();
String bluetoothIface = findIface(tethered, mBluetoothRegexs);
if (bluetoothIface != null &&
@@ -528,16 +516,13 @@ public class TetherSettings extends SettingsPreferenceFragment
mWifiConfig = mDialog.getConfig();
if (mWifiConfig != null) {
/**
- * if soft AP is running, bring up with new config
- * else update the configuration alone
+ * if soft AP is stopped, bring up
+ * else restart with new config
+ * TODO: update config on a running access point when framework support is added
*/
if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
+ mWifiManager.setWifiApEnabled(null, false);
mWifiManager.setWifiApEnabled(mWifiConfig, true);
- /**
- * There is no tether notification on changing AP
- * configuration. Update status with new config.
- */
- mWifiApEnabler.updateConfigSummary(mWifiConfig);
} else {
mWifiManager.setWifiApConfiguration(mWifiConfig);
}
diff --git a/src/com/android/settings/TextToSpeechSettings.java b/src/com/android/settings/TextToSpeechSettings.java
index 62edac9..d76f08f 100644
--- a/src/com/android/settings/TextToSpeechSettings.java
+++ b/src/com/android/settings/TextToSpeechSettings.java
@@ -21,28 +21,23 @@ import static android.provider.Settings.Secure.TTS_DEFAULT_LANG;
import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
import static android.provider.Settings.Secure.TTS_DEFAULT_VARIANT;
-import static android.provider.Settings.Secure.TTS_ENABLED_PLUGINS;
import static android.provider.Settings.Secure.TTS_USE_DEFAULTS;
-import android.app.Activity;
import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
-import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.EngineInfo;
+import android.speech.tts.TtsEngines;
+import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
@@ -56,7 +51,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private static final String TAG = "TextToSpeechSettings";
- private static final String SYSTEM_TTS = "com.svox.pico";
private static final String KEY_TTS_PLAY_EXAMPLE = "tts_play_example";
private static final String KEY_TTS_INSTALL_DATA = "tts_install_data";
private static final String KEY_TTS_USE_DEFAULT = "toggle_use_default_tts_settings";
@@ -65,6 +59,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private static final String KEY_TTS_DEFAULT_COUNTRY = "tts_default_country";
private static final String KEY_TTS_DEFAULT_VARIANT = "tts_default_variant";
private static final String KEY_TTS_DEFAULT_SYNTH = "tts_default_synth";
+ private static final String KEY_TTS_ENGINE_SETTINGS = "tts_engine_settings";
private static final String KEY_PLUGIN_ENABLED_PREFIX = "ENABLED_";
private static final String KEY_PLUGIN_SETTINGS_PREFIX = "SETTINGS_";
@@ -76,19 +71,18 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private static final String LOCALE_DELIMITER = "-";
- private static final String FALLBACK_TTS_DEFAULT_SYNTH =
- TextToSpeech.Engine.DEFAULT_SYNTH;
+ private Preference mPlayExample = null;
+
+ private ListPreference mDefaultRatePref = null;
+ private ListPreference mDefaultLocPref = null;
+ private ListPreference mDefaultSynthPref = null;
+
+ private Preference mInstallData = null;
+ private Preference mEngineSettings = null;
- private Preference mPlayExample = null;
- private Preference mInstallData = null;
- private CheckBoxPreference mUseDefaultPref = null;
- private ListPreference mDefaultRatePref = null;
- private ListPreference mDefaultLocPref = null;
- private ListPreference mDefaultSynthPref = null;
private String mDefaultLanguage = null;
private String mDefaultCountry = null;
private String mDefaultLocVariant = null;
- private String mDefaultEng = "";
private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE;
// Index of the current string to use for the demo.
@@ -98,6 +92,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
private boolean mVoicesMissing = false;
private TextToSpeech mTts = null;
+ private TtsEngines mEnginesHelper = null;
private boolean mTtsStarted = false;
/**
@@ -112,10 +107,7 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.tts_settings);
- final Activity activity = getActivity();
- addEngineSpecificSettings(activity);
-
- activity.setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
+ getActivity().setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
mEnableDemo = false;
mTtsStarted = false;
@@ -125,10 +117,25 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
mDefaultCountry = currentLocale.getISO3Country();
mDefaultLocVariant = currentLocale.getVariant();
- mTts = new TextToSpeech(activity, this);
- initClickers();
- }
+ mPlayExample = findPreference(KEY_TTS_PLAY_EXAMPLE);
+ mPlayExample.setOnPreferenceClickListener(this);
+ mInstallData = findPreference(KEY_TTS_INSTALL_DATA);
+ mInstallData.setOnPreferenceClickListener(this);
+
+ mDefaultSynthPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_SYNTH);
+ mDefaultRatePref = (ListPreference) findPreference(KEY_TTS_DEFAULT_RATE);
+ mDefaultLocPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_LANG);
+
+ mEngineSettings = findPreference(KEY_TTS_ENGINE_SETTINGS);
+ mEngineSettings.setEnabled(false);
+
+ mTts = new TextToSpeech(getActivity().getApplicationContext(), this);
+ mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
+
+ initDefaultSettings();
+ initEngineSpecificSettings();
+ }
@Override
public void onStart() {
@@ -142,7 +149,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
}
}
-
@Override
public void onDestroy() {
super.onDestroy();
@@ -165,97 +171,39 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
}
}
- private void addEngineSpecificSettings(Context context) {
- PreferenceGroup enginesCategory = (PreferenceGroup) findPreference("tts_engines_section");
- Intent intent = new Intent("android.intent.action.START_TTS_ENGINE");
- ResolveInfo[] enginesArray = new ResolveInfo[0];
- PackageManager pm = getPackageManager();
- enginesArray = pm.queryIntentActivities(intent, 0).toArray(enginesArray);
- for (int i = 0; i < enginesArray.length; i++) {
- String prefKey = "";
- final String pluginPackageName = enginesArray[i].activityInfo.packageName;
- if (!enginesArray[i].activityInfo.packageName.equals(SYSTEM_TTS)) {
- CheckBoxPreference chkbxPref = new CheckBoxPreference(context);
- prefKey = KEY_PLUGIN_ENABLED_PREFIX + pluginPackageName;
- chkbxPref.setKey(prefKey);
- chkbxPref.setTitle(enginesArray[i].loadLabel(pm));
- enginesCategory.addPreference(chkbxPref);
- }
- if (pluginHasSettings(pluginPackageName)) {
- Preference pref = new Preference(context);
- prefKey = KEY_PLUGIN_SETTINGS_PREFIX + pluginPackageName;
- pref.setKey(prefKey);
- pref.setTitle(enginesArray[i].loadLabel(pm));
- CharSequence settingsLabel = getResources().getString(
- R.string.tts_engine_name_settings, enginesArray[i].loadLabel(pm));
- pref.setSummary(settingsLabel);
- pref.setOnPreferenceClickListener(new OnPreferenceClickListener(){
- public boolean onPreferenceClick(Preference preference){
- Intent i = new Intent();
- i.setClassName(pluginPackageName,
- pluginPackageName + ".EngineSettings");
- startActivity(i);
- return true;
- }
- });
- enginesCategory.addPreference(pref);
- }
- }
- }
-
- private boolean pluginHasSettings(String pluginPackageName) {
- PackageManager pm = getPackageManager();
- Intent i = new Intent();
- i.setClassName(pluginPackageName, pluginPackageName + ".EngineSettings");
- if (pm.resolveActivity(i, PackageManager.MATCH_DEFAULT_ONLY) != null){
- return true;
- }
- return false;
- }
+ private void initEngineSpecificSettings() {
+ final String engineName = mEnginesHelper.getDefaultEngine();
+ final EngineInfo engine = mEnginesHelper.getEngineInfo(engineName);
+ mEngineSettings.setTitle(getResources().getString(R.string.tts_engine_settings_title,
+ engine.label));
- private void initClickers() {
- mPlayExample = findPreference(KEY_TTS_PLAY_EXAMPLE);
- mPlayExample.setOnPreferenceClickListener(this);
+ final Intent settingsIntent = mEnginesHelper.getSettingsIntent(engineName);
+ if (settingsIntent != null) {
+ mEngineSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ public boolean onPreferenceClick(Preference preference) {
+ startActivity(settingsIntent);
+ return true;
+ }
+ });
+ mEngineSettings.setEnabled(true);
+ } else {
+ mEngineSettings.setEnabled(false);
+ }
- mInstallData = findPreference(KEY_TTS_INSTALL_DATA);
- mInstallData.setOnPreferenceClickListener(this);
}
-
private void initDefaultSettings() {
ContentResolver resolver = getContentResolver();
// Find the default TTS values in the settings, initialize and store the
// settings if they are not found.
- // "Use Defaults"
- int useDefault = 0;
- mUseDefaultPref = (CheckBoxPreference) findPreference(KEY_TTS_USE_DEFAULT);
- try {
- useDefault = Settings.Secure.getInt(resolver, TTS_USE_DEFAULTS);
- } catch (SettingNotFoundException e) {
- // "use default" setting not found, initialize it
- useDefault = TextToSpeech.Engine.USE_DEFAULTS;
- Settings.Secure.putInt(resolver, TTS_USE_DEFAULTS, useDefault);
- }
- mUseDefaultPref.setChecked(useDefault == 1);
- mUseDefaultPref.setOnPreferenceChangeListener(this);
-
// Default synthesis engine
- mDefaultSynthPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_SYNTH);
loadEngines();
mDefaultSynthPref.setOnPreferenceChangeListener(this);
- String engine = Settings.Secure.getString(resolver, TTS_DEFAULT_SYNTH);
- if (engine == null) {
- // TODO move FALLBACK_TTS_DEFAULT_SYNTH to TextToSpeech
- engine = FALLBACK_TTS_DEFAULT_SYNTH;
- Settings.Secure.putString(resolver, TTS_DEFAULT_SYNTH, engine);
- }
- mDefaultEng = engine;
// Default rate
- mDefaultRatePref = (ListPreference) findPreference(KEY_TTS_DEFAULT_RATE);
try {
mDefaultRate = Settings.Secure.getInt(resolver, TTS_DEFAULT_RATE);
} catch (SettingNotFoundException e) {
@@ -265,34 +213,27 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
}
mDefaultRatePref.setValue(String.valueOf(mDefaultRate));
mDefaultRatePref.setOnPreferenceChangeListener(this);
- // apply the default rate so the TTS demo in the Settings screen uses it, even if
- // the use of default settings is not enforced
- mTts.setSpeechRate(mDefaultRate/100.0f);
// Default language / country / variant : these three values map to a single ListPref
// representing the matching Locale
- mDefaultLocPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_LANG);
initDefaultLang();
mDefaultLocPref.setOnPreferenceChangeListener(this);
}
-
/**
* Ask the current default engine to launch the matching CHECK_TTS_DATA activity
* to check the required TTS files are properly installed.
*/
private void checkVoiceData() {
- PackageManager pm = getPackageManager();
- Intent intent = new Intent();
- intent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
- List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
- // query only the package that matches that of the default engine
- for (int i = 0; i < resolveInfos.size(); i++) {
- ActivityInfo currentActivityInfo = resolveInfos.get(i).activityInfo;
- if (mDefaultEng.equals(currentActivityInfo.packageName)) {
- intent.setClassName(mDefaultEng, currentActivityInfo.name);
- this.startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK);
- }
+ String defaultEngine = mTts.getDefaultEngine();
+ if (TextUtils.isEmpty(defaultEngine)) return;
+ Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
+ intent.setPackage(defaultEngine);
+ try {
+ Log.v(TAG, "Checking voice data: " + intent.toUri(0));
+ startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Failed to check TTS data, no acitivty found for " + intent + ")");
}
}
@@ -302,18 +243,16 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
* so the required TTS files are properly installed.
*/
private void installVoiceData() {
- PackageManager pm = getPackageManager();
- Intent intent = new Intent();
- intent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
+ String defaultEngine = mTts.getDefaultEngine();
+ if (TextUtils.isEmpty(defaultEngine)) return;
+ Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
- // query only the package that matches that of the default engine
- for (int i = 0; i < resolveInfos.size(); i++) {
- ActivityInfo currentActivityInfo = resolveInfos.get(i).activityInfo;
- if (mDefaultEng.equals(currentActivityInfo.packageName)) {
- intent.setClassName(mDefaultEng, currentActivityInfo.name);
- this.startActivity(intent);
- }
+ intent.setPackage(defaultEngine);
+ try {
+ Log.v(TAG, "Installing voice data: " + intent.toUri(0));
+ startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")");
}
}
@@ -322,26 +261,22 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
* spoken to the user.
*/
private void getSampleText() {
- PackageManager pm = getPackageManager();
- Intent intent = new Intent();
- // TODO (clchen): Replace Intent string with the actual
- // Intent defined in the list of platform Intents.
- intent.setAction("android.speech.tts.engine.GET_SAMPLE_TEXT");
+ String defaultEngine = mTts.getDefaultEngine();
+ if (TextUtils.isEmpty(defaultEngine)) return;
+ Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT);
intent.putExtra("language", mDefaultLanguage);
intent.putExtra("country", mDefaultCountry);
intent.putExtra("variant", mDefaultLocVariant);
- List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
- // query only the package that matches that of the default engine
- for (int i = 0; i < resolveInfos.size(); i++) {
- ActivityInfo currentActivityInfo = resolveInfos.get(i).activityInfo;
- if (mDefaultEng.equals(currentActivityInfo.packageName)) {
- intent.setClassName(mDefaultEng, currentActivityInfo.name);
- this.startActivityForResult(intent, GET_SAMPLE_TEXT);
- }
+ intent.setPackage(defaultEngine);
+
+ try {
+ Log.v(TAG, "Getting sample text: " + intent.toUri(0));
+ startActivityForResult(intent, GET_SAMPLE_TEXT);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Failed to get sample text, no acitivty found for " + intent + ")");
}
}
-
/**
* Called when the TTS engine is initialized.
*/
@@ -358,7 +293,6 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
mDefaultLocVariant = new String();
}
mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant));
- initDefaultSettings();
updateWidgetState();
checkVoiceData();
mTtsStarted = true;
@@ -370,142 +304,155 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
updateWidgetState();
}
-
/**
* Called when voice data integrity check returns
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
- if (data == null){
- // The CHECK_TTS_DATA activity for the plugin did not run properly;
- // disable the preview and install controls and return.
- mEnableDemo = false;
- mVoicesMissing = false;
- updateWidgetState();
- return;
- }
- ArrayList<String> available =
- data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
- ArrayList<String> unavailable =
- data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES);
- if ((available == null) || (unavailable == null)){
- // The CHECK_TTS_DATA activity for the plugin did not run properly;
- // disable the preview and install controls and return.
- mEnableDemo = false;
- mVoicesMissing = false;
- updateWidgetState();
- return;
+ onVoiceDataIntegrityCheckDone(data);
+ } else if (requestCode == GET_SAMPLE_TEXT) {
+ onSampleTextReceived(resultCode, data);
+ }
+ }
+
+ private void onVoiceDataIntegrityCheckDone(Intent data) {
+ if (data == null){
+ Log.e(TAG, "TTS data check failed data = null");
+ // The CHECK_TTS_DATA activity for the plugin did not run properly;
+ // disable the preview and install controls and return.
+ mEnableDemo = false;
+ mVoicesMissing = false;
+ updateWidgetState();
+ return;
+ }
+ Log.v(TAG, "TTS data check completed, data = " + data.toUri(0));
+ ArrayList<String> available =
+ data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
+ ArrayList<String> unavailable =
+ data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES);
+ if (available == null || unavailable == null){
+ Log.e(TAG, "TTS data check failed (available == == null)");
+ // The CHECK_TTS_DATA activity for the plugin did not run properly;
+ // disable the preview and install controls and return.
+ mEnableDemo = false;
+ mVoicesMissing = false;
+ updateWidgetState();
+ return;
+ }
+ if (available.size() > 0){
+ if (mTts == null) {
+ mTts = new TextToSpeech(getActivity(), this);
}
- if (available.size() > 0){
- if (mTts == null) {
- mTts = new TextToSpeech(getActivity(), this);
- }
- ListPreference ttsLanguagePref =
- (ListPreference) findPreference("tts_default_lang");
- CharSequence[] entries = new CharSequence[available.size()];
- CharSequence[] entryValues = new CharSequence[available.size()];
- int selectedLanguageIndex = -1;
- String selectedLanguagePref = mDefaultLanguage;
- if (mDefaultCountry.length() > 0) {
- selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER +
- mDefaultCountry;
- }
- if (mDefaultLocVariant.length() > 0) {
- selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER +
- mDefaultLocVariant;
- }
- for (int i = 0; i < available.size(); i++) {
- String[] langCountryVariant = available.get(i).split("-");
- Locale loc = null;
- if (langCountryVariant.length == 1){
- loc = new Locale(langCountryVariant[0]);
- } else if (langCountryVariant.length == 2){
- loc = new Locale(langCountryVariant[0], langCountryVariant[1]);
- } else if (langCountryVariant.length == 3){
- loc = new Locale(langCountryVariant[0], langCountryVariant[1],
- langCountryVariant[2]);
- }
- if (loc != null){
- entries[i] = loc.getDisplayName();
- entryValues[i] = available.get(i);
- if (entryValues[i].equals(selectedLanguagePref)) {
- selectedLanguageIndex = i;
- }
- }
- }
- ttsLanguagePref.setEntries(entries);
- ttsLanguagePref.setEntryValues(entryValues);
- if (selectedLanguageIndex > -1) {
- ttsLanguagePref.setValueIndex(selectedLanguageIndex);
- }
- mEnableDemo = true;
- // Make sure that the default language can be used.
- int languageResult = mTts.setLanguage(
+
+ updateDefaultLocPref(available);
+
+ mEnableDemo = true;
+ // Make sure that the default language can be used.
+ int languageResult = mTts.setLanguage(
+ new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant));
+ if (languageResult < TextToSpeech.LANG_AVAILABLE){
+ Locale currentLocale = Locale.getDefault();
+ mDefaultLanguage = currentLocale.getISO3Language();
+ mDefaultCountry = currentLocale.getISO3Country();
+ mDefaultLocVariant = currentLocale.getVariant();
+ languageResult = mTts.setLanguage(
new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant));
+ // If the default Locale isn't supported, just choose the first available
+ // language so that there is at least something.
if (languageResult < TextToSpeech.LANG_AVAILABLE){
- Locale currentLocale = Locale.getDefault();
- mDefaultLanguage = currentLocale.getISO3Language();
- mDefaultCountry = currentLocale.getISO3Country();
- mDefaultLocVariant = currentLocale.getVariant();
- languageResult = mTts.setLanguage(
+ parseLocaleInfo(mDefaultLocPref.getEntryValues()[0].toString());
+ mTts.setLanguage(
new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant));
- // If the default Locale isn't supported, just choose the first available
- // language so that there is at least something.
- if (languageResult < TextToSpeech.LANG_AVAILABLE){
- parseLocaleInfo(ttsLanguagePref.getEntryValues()[0].toString());
- mTts.setLanguage(
- new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant));
- }
- ContentResolver resolver = getContentResolver();
- Settings.Secure.putString(resolver, TTS_DEFAULT_LANG, mDefaultLanguage);
- Settings.Secure.putString(resolver, TTS_DEFAULT_COUNTRY, mDefaultCountry);
- Settings.Secure.putString(resolver, TTS_DEFAULT_VARIANT, mDefaultLocVariant);
}
- } else {
- mEnableDemo = false;
+ ContentResolver resolver = getContentResolver();
+ Settings.Secure.putString(resolver, TTS_DEFAULT_LANG, mDefaultLanguage);
+ Settings.Secure.putString(resolver, TTS_DEFAULT_COUNTRY, mDefaultCountry);
+ Settings.Secure.putString(resolver, TTS_DEFAULT_VARIANT, mDefaultLocVariant);
}
+ } else {
+ mEnableDemo = false;
+ }
- if (unavailable.size() > 0){
- mVoicesMissing = true;
- } else {
- mVoicesMissing = false;
- }
+ if (unavailable.size() > 0){
+ mVoicesMissing = true;
+ } else {
+ mVoicesMissing = false;
+ }
- updateWidgetState();
- } else if (requestCode == GET_SAMPLE_TEXT) {
- if (resultCode == TextToSpeech.LANG_AVAILABLE) {
- String sample = getActivity().getString(R.string.tts_demo);
- if ((data != null) && (data.getStringExtra("sampleText") != null)) {
- sample = data.getStringExtra("sampleText");
- }
- if (mTts != null) {
- mTts.speak(sample, TextToSpeech.QUEUE_FLUSH, null);
+ updateWidgetState();
+ }
+
+ private void updateDefaultLocPref(ArrayList<String> availableLangs) {
+ CharSequence[] entries = new CharSequence[availableLangs.size()];
+ CharSequence[] entryValues = new CharSequence[availableLangs.size()];
+ int selectedLanguageIndex = -1;
+ String selectedLanguagePref = mDefaultLanguage;
+ if (mDefaultCountry.length() > 0) {
+ selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER +
+ mDefaultCountry;
+ }
+ if (mDefaultLocVariant.length() > 0) {
+ selectedLanguagePref = selectedLanguagePref + LOCALE_DELIMITER +
+ mDefaultLocVariant;
+ }
+ for (int i = 0; i < availableLangs.size(); i++) {
+ String[] langCountryVariant = availableLangs.get(i).split("-");
+ Locale loc = null;
+ if (langCountryVariant.length == 1){
+ loc = new Locale(langCountryVariant[0]);
+ } else if (langCountryVariant.length == 2){
+ loc = new Locale(langCountryVariant[0], langCountryVariant[1]);
+ } else if (langCountryVariant.length == 3){
+ loc = new Locale(langCountryVariant[0], langCountryVariant[1],
+ langCountryVariant[2]);
+ }
+ if (loc != null){
+ entries[i] = loc.getDisplayName();
+ entryValues[i] = availableLangs.get(i);
+ if (entryValues[i].equals(selectedLanguagePref)) {
+ selectedLanguageIndex = i;
}
- } else {
- // TODO: Display an error here to the user.
- Log.e(TAG, "Did not have a sample string for the requested language");
}
}
+ mDefaultLocPref.setEntries(entries);
+ mDefaultLocPref.setEntryValues(entryValues);
+ if (selectedLanguageIndex > -1) {
+ mDefaultLocPref.setValueIndex(selectedLanguageIndex);
+ }
+ }
+
+ private void onSampleTextReceived(int resultCode, Intent data) {
+ if (resultCode == TextToSpeech.LANG_AVAILABLE) {
+ String sample = getActivity().getString(R.string.tts_demo);
+ if (data != null && data.getStringExtra("sampleText") != null) {
+ sample = data.getStringExtra("sampleText");
+ }
+ Log.v(TAG, "Got sample text: " + sample);
+ if (mTts != null) {
+ mTts.speak(sample, TextToSpeech.QUEUE_FLUSH, null);
+ }
+ } else {
+ // TODO: Display an error here to the user.
+ Log.e(TAG, "Did not have a sample string for the requested language");
+ }
}
public boolean onPreferenceChange(Preference preference, Object objValue) {
if (KEY_TTS_USE_DEFAULT.equals(preference.getKey())) {
// "Use Defaults"
- int value = (Boolean)objValue ? 1 : 0;
- Settings.Secure.putInt(getContentResolver(), TTS_USE_DEFAULTS,
- value);
- Log.i(TAG, "TTS use default settings is "+objValue.toString());
+ int value = ((Boolean) objValue) ? 1 : 0;
+ Settings.Secure.putInt(getContentResolver(), TTS_USE_DEFAULTS, value);
+ Log.i(TAG, "TTS 'use default' settings changed, now " + value);
} else if (KEY_TTS_DEFAULT_RATE.equals(preference.getKey())) {
// Default rate
mDefaultRate = Integer.parseInt((String) objValue);
try {
- Settings.Secure.putInt(getContentResolver(),
- TTS_DEFAULT_RATE, mDefaultRate);
+ Settings.Secure.putInt(getContentResolver(), TTS_DEFAULT_RATE, mDefaultRate);
if (mTts != null) {
- mTts.setSpeechRate(mDefaultRate/100.0f);
+ mTts.setSpeechRate(mDefaultRate / 100.0f);
}
- Log.i(TAG, "TTS default rate is " + mDefaultRate);
+ Log.v(TAG, "TTS default rate changed, now " + mDefaultRate);
} catch (NumberFormatException e) {
Log.e(TAG, "could not persist default TTS rate setting", e);
}
@@ -522,19 +469,24 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
mTts.setLanguage(new Locale(mDefaultLanguage, mDefaultCountry, mDefaultLocVariant));
}
int newIndex = mDefaultLocPref.findIndexOfValue((String)objValue);
- Log.v("Settings", " selected is " + newIndex);
+ Log.v(TAG, " selected is " + newIndex);
mDemoStringIndex = newIndex > -1 ? newIndex : 0;
} else if (KEY_TTS_DEFAULT_SYNTH.equals(preference.getKey())) {
- mDefaultEng = objValue.toString();
- Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, mDefaultEng);
- if (mTts != null) {
- mTts.setEngineByPackageName(mDefaultEng);
- mEnableDemo = false;
- mVoicesMissing = false;
- updateWidgetState();
- checkVoiceData();
+ final String name = objValue.toString();
+ final EngineInfo info = mEnginesHelper.getEngineInfo(name);
+
+ if (info.system) {
+ // For system engines, do away with the alert dialog.
+ updateDefaultEngine(name);
+ initEngineSpecificSettings();
+ } else {
+ // For all other engines, display a warning message before
+ // turning them on.
+ displayDataAlert(preference, name);
}
- Log.v("Settings", "The default synth is: " + objValue.toString());
+
+ // We'll deal with updating the UI ourselves.
+ return false;
}
return true;
@@ -550,61 +502,18 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
// the actual speaking
getSampleText();
return true;
- }
- if (preference == mInstallData) {
+ } else if (preference == mInstallData) {
installVoiceData();
// quit this activity so it needs to be restarted after installation of the voice data
finish();
return true;
}
- return false;
- }
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (Utils.isMonkeyRunning()) {
- return false;
- }
-
- if (preference instanceof CheckBoxPreference) {
- final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
- if (!chkPref.getKey().equals(KEY_TTS_USE_DEFAULT)){
- if (chkPref.isChecked()) {
- chkPref.setChecked(false);
- AlertDialog d = (new AlertDialog.Builder(getActivity()))
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(
- getActivity().getString(R.string.tts_engine_security_warning,
- chkPref.getTitle()))
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- chkPref.setChecked(true);
- loadEngines();
- }
- })
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- }
- })
- .create();
- d.show();
- } else {
- loadEngines();
- }
- return true;
- }
- }
return false;
}
-
private void updateWidgetState() {
mPlayExample.setEnabled(mEnableDemo);
- mUseDefaultPref.setEnabled(mEnableDemo);
mDefaultRatePref.setEnabled(mEnableDemo);
mDefaultLocPref.setEnabled(mEnableDemo);
@@ -617,14 +526,18 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
mDefaultLanguage = "";
mDefaultCountry = "";
mDefaultLocVariant = "";
- if (tokenizer.hasMoreTokens()) {
- mDefaultLanguage = tokenizer.nextToken().trim();
- }
- if (tokenizer.hasMoreTokens()) {
- mDefaultCountry = tokenizer.nextToken().trim();
- }
- if (tokenizer.hasMoreTokens()) {
- mDefaultLocVariant = tokenizer.nextToken().trim();
+
+ if (locale != null) {
+ String[] components = locale.split(LOCALE_DELIMITER);
+ if (components.length > 0) {
+ mDefaultLanguage = components[0];
+ }
+ if (components.length > 1) {
+ mDefaultCountry = components[1];
+ }
+ if (components.length > 2) {
+ mDefaultLocVariant = components[2];
+ }
}
}
@@ -716,50 +629,81 @@ public class TextToSpeechSettings extends SettingsPreferenceFragment implements
Settings.Secure.putString(resolver, TTS_DEFAULT_VARIANT, DEFAULT_VARIANT_VAL);
}
-
private void loadEngines() {
- mDefaultSynthPref = (ListPreference) findPreference(KEY_TTS_DEFAULT_SYNTH);
-
- // TODO (clchen): Try to see if it is possible to be more efficient here
- // and not search for plugins again.
- Intent intent = new Intent("android.intent.action.START_TTS_ENGINE");
- ResolveInfo[] enginesArray = new ResolveInfo[0];
- PackageManager pm = getPackageManager();
- enginesArray = pm.queryIntentActivities(intent, 0).toArray(enginesArray);
- ArrayList<CharSequence> entries = new ArrayList<CharSequence>();
- ArrayList<CharSequence> values = new ArrayList<CharSequence>();
- String enabledEngines = "";
- for (int i = 0; i < enginesArray.length; i++) {
- String pluginPackageName = enginesArray[i].activityInfo.packageName;
- if (pluginPackageName.equals(SYSTEM_TTS)) {
- entries.add(enginesArray[i].loadLabel(pm));
- values.add(pluginPackageName);
- } else {
- CheckBoxPreference pref = (CheckBoxPreference) findPreference(
- KEY_PLUGIN_ENABLED_PREFIX + pluginPackageName);
- if ((pref != null) && pref.isChecked()){
- entries.add(enginesArray[i].loadLabel(pm));
- values.add(pluginPackageName);
- enabledEngines = enabledEngines + pluginPackageName + " ";
- }
- }
+ List<EngineInfo> engines = mEnginesHelper.getEngines();
+ CharSequence entries[] = new CharSequence[engines.size()];
+ CharSequence values[] = new CharSequence[engines.size()];
+
+ final int count = engines.size();
+ for (int i = 0; i < count; ++i) {
+ final EngineInfo engine = engines.get(i);
+ entries[i] = engine.label;
+ values[i] = engine.name;
}
- ContentResolver resolver = getContentResolver();
- Settings.Secure.putString(resolver, TTS_ENABLED_PLUGINS, enabledEngines);
-
- CharSequence entriesArray[] = new CharSequence[entries.size()];
- CharSequence valuesArray[] = new CharSequence[values.size()];
- mDefaultSynthPref.setEntries(entries.toArray(entriesArray));
- mDefaultSynthPref.setEntryValues(values.toArray(valuesArray));
+ mDefaultSynthPref.setEntries(entries);
+ mDefaultSynthPref.setEntryValues(values);
// Set the selected engine based on the saved preference
String selectedEngine = Settings.Secure.getString(getContentResolver(), TTS_DEFAULT_SYNTH);
int selectedEngineIndex = mDefaultSynthPref.findIndexOfValue(selectedEngine);
if (selectedEngineIndex == -1){
- selectedEngineIndex = mDefaultSynthPref.findIndexOfValue(SYSTEM_TTS);
+ selectedEngineIndex = mDefaultSynthPref.findIndexOfValue(
+ mEnginesHelper.getHighestRankedEngineName());
+ }
+ if (selectedEngineIndex >= 0) {
+ mDefaultSynthPref.setValueIndex(selectedEngineIndex);
+ }
+ }
+
+ private void displayDataAlert(Preference pref, final String key) {
+ Log.v(TAG, "Displaying data alert for :" + key);
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(android.R.string.dialog_alert_title);
+ builder.setIcon(android.R.drawable.ic_dialog_alert);
+ builder.setMessage(getActivity().getString(
+ R.string.tts_engine_security_warning, pref.getTitle()));
+ builder.setCancelable(true);
+ builder.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ updateDefaultEngine(key);
+ loadEngines();
+ initEngineSpecificSettings();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ private void updateDefaultEngine(String engine) {
+ Log.v(TAG, "Updating default synth to : " + engine);
+ if (mTts != null) {
+ try {
+ mTts.shutdown();
+ mTts = null;
+ } catch (Exception e) {
+ Log.e(TAG, "Error shutting down TTS engine" + e);
+ }
}
- mDefaultSynthPref.setValueIndex(selectedEngineIndex);
+
+ mTts = new TextToSpeech(getActivity().getApplicationContext(), this, engine);
+ mEnableDemo = false;
+ mVoicesMissing = false;
+
+ // Persist this value to settings and update the UI before we check
+ // voice data because if the TTS class connected without any exception, "engine"
+ // will be the default engine irrespective of whether the voice check
+ // passes or not.
+ Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine);
+ mDefaultSynthPref.setValue(engine);
+ updateWidgetState();
+
+ checkVoiceData();
+
+ Log.v(TAG, "The default synth is now: " + engine);
}
}
diff --git a/src/com/android/settings/TrustedCredentialsSettings.java b/src/com/android/settings/TrustedCredentialsSettings.java
new file mode 100644
index 0000000..687663a
--- /dev/null
+++ b/src/com/android/settings/TrustedCredentialsSettings.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.Fragment;
+import android.content.DialogInterface;
+import android.net.http.SslCertificate;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TabHost;
+import android.widget.TextView;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.harmony.xnet.provider.jsse.TrustedCertificateStore;
+
+public class TrustedCredentialsSettings extends Fragment {
+
+ private static final String TAG = "TrustedCredentialsSettings";
+
+ private enum Tab {
+ SYSTEM("system",
+ R.string.trusted_credentials_system_tab,
+ R.id.system_tab,
+ R.id.system_progress,
+ R.id.system_list,
+ true),
+ USER("user",
+ R.string.trusted_credentials_user_tab,
+ R.id.user_tab,
+ R.id.user_progress,
+ R.id.user_list,
+ false);
+
+ private final String mTag;
+ private final int mLabel;
+ private final int mView;
+ private final int mProgress;
+ private final int mList;
+ private final boolean mCheckbox;
+ private Tab(String tag, int label, int view, int progress, int list, boolean checkbox) {
+ mTag = tag;
+ mLabel = label;
+ mView = view;
+ mProgress = progress;
+ mList = list;
+ mCheckbox = checkbox;
+ }
+ private Set<String> getAliases(TrustedCertificateStore store) {
+ switch (this) {
+ case SYSTEM:
+ return store.allSystemAliases();
+ case USER:
+ return store.userAliases();
+ }
+ throw new AssertionError();
+ }
+ private boolean deleted(TrustedCertificateStore store, String alias) {
+ switch (this) {
+ case SYSTEM:
+ return !store.containsAlias(alias);
+ case USER:
+ return false;
+ }
+ throw new AssertionError();
+ }
+ private int getButtonLabel(CertHolder certHolder) {
+ switch (this) {
+ case SYSTEM:
+ if (certHolder.mDeleted) {
+ return R.string.trusted_credentials_enable_label;
+ }
+ return R.string.trusted_credentials_disable_label;
+ case USER:
+ return R.string.trusted_credentials_remove_label;
+ }
+ throw new AssertionError();
+ }
+ private int getButtonConfirmation(CertHolder certHolder) {
+ switch (this) {
+ case SYSTEM:
+ if (certHolder.mDeleted) {
+ return R.string.trusted_credentials_enable_confirmation;
+ }
+ return R.string.trusted_credentials_disable_confirmation;
+ case USER:
+ return R.string.trusted_credentials_remove_confirmation;
+ }
+ throw new AssertionError();
+ }
+ private void postOperationUpdate(boolean ok, CertHolder certHolder) {
+ if (ok) {
+ if (certHolder.mTab.mCheckbox) {
+ certHolder.mDeleted = !certHolder.mDeleted;
+ } else {
+ certHolder.mAdapter.mCertHolders.remove(certHolder);
+ }
+ certHolder.mAdapter.notifyDataSetChanged();
+ } else {
+ // bail, reload to reset to known state
+ certHolder.mAdapter.load();
+ }
+ }
+ }
+
+ // be careful not to use this on the UI thread since it is does file operations
+ private final TrustedCertificateStore mStore = new TrustedCertificateStore();
+
+ private TabHost mTabHost;
+
+ @Override public View onCreateView(
+ LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
+ mTabHost = (TabHost) inflater.inflate(R.layout.trusted_credentials, parent, false);
+ mTabHost.setup();
+ addTab(Tab.SYSTEM);
+ // TODO add Install button on Tab.USER to go to CertInstaller like KeyChainActivity
+ addTab(Tab.USER);
+ return mTabHost;
+ }
+
+ private void addTab(Tab tab) {
+ TabHost.TabSpec systemSpec = mTabHost.newTabSpec(tab.mTag)
+ .setIndicator(getActivity().getString(tab.mLabel))
+ .setContent(tab.mView);
+ mTabHost.addTab(systemSpec);
+
+ ListView lv = (ListView) mTabHost.findViewById(tab.mList);
+ final TrustedCertificateAdapter adapter = new TrustedCertificateAdapter(tab);
+ lv.setAdapter(adapter);
+ lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
+ showCertDialog(adapter.getItem(pos));
+ }
+ });
+ }
+
+ private class TrustedCertificateAdapter extends BaseAdapter {
+ private final List<CertHolder> mCertHolders = new ArrayList<CertHolder>();
+ private final Tab mTab;
+ private TrustedCertificateAdapter(Tab tab) {
+ mTab = tab;
+ load();
+ }
+ private void load() {
+ new AliasLoader().execute();
+ }
+ @Override public int getCount() {
+ return mCertHolders.size();
+ }
+ @Override public CertHolder getItem(int position) {
+ return mCertHolders.get(position);
+ }
+ @Override public long getItemId(int position) {
+ return position;
+ }
+ @Override public View getView(int position, View view, ViewGroup parent) {
+ ViewHolder holder;
+ if (view == null) {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ view = inflater.inflate(R.layout.trusted_credential, parent, false);
+ holder = new ViewHolder();
+ holder.mSubjectPrimaryView = (TextView)
+ view.findViewById(R.id.trusted_credential_subject_primary);
+ holder.mSubjectSecondaryView = (TextView)
+ view.findViewById(R.id.trusted_credential_subject_secondary);
+ holder.mCheckBox = (CheckBox) view.findViewById(R.id.trusted_credential_status);
+ view.setTag(holder);
+ } else {
+ holder = (ViewHolder) view.getTag();
+ }
+ CertHolder certHolder = mCertHolders.get(position);
+ holder.mSubjectPrimaryView.setText(certHolder.mSubjectPrimary);
+ holder.mSubjectSecondaryView.setText(certHolder.mSubjectSecondary);
+ if (mTab.mCheckbox) {
+ holder.mCheckBox.setChecked(!certHolder.mDeleted);
+ holder.mCheckBox.setVisibility(View.VISIBLE);
+ }
+ return view;
+ };
+
+ private class AliasLoader extends AsyncTask<Void, Integer, List<CertHolder>> {
+ ProgressBar mProgressBar;
+ View mList;
+ @Override protected void onPreExecute() {
+ View content = mTabHost.getTabContentView();
+ mProgressBar = (ProgressBar) content.findViewById(mTab.mProgress);
+ mList = content.findViewById(mTab.mList);
+ mProgressBar.setVisibility(View.VISIBLE);
+ mList.setVisibility(View.GONE);
+ }
+ @Override protected List<CertHolder> doInBackground(Void... params) {
+ Set<String> aliases = mTab.getAliases(mStore);
+ int max = aliases.size();
+ int progress = 0;
+ List<CertHolder> certHolders = new ArrayList<CertHolder>(max);
+ for (String alias : aliases) {
+ X509Certificate cert = (X509Certificate) mStore.getCertificate(alias, true);
+ certHolders.add(new CertHolder(mStore,
+ TrustedCertificateAdapter.this,
+ mTab,
+ alias,
+ cert));
+ publishProgress(++progress, max);
+ }
+ Collections.sort(certHolders);
+ return certHolders;
+ }
+ @Override protected void onProgressUpdate(Integer... progressAndMax) {
+ int progress = progressAndMax[0];
+ int max = progressAndMax[1];
+ if (max != mProgressBar.getMax()) {
+ mProgressBar.setMax(max);
+ }
+ mProgressBar.setProgress(progress);
+ }
+ @Override protected void onPostExecute(List<CertHolder> certHolders) {
+ mCertHolders.clear();
+ mCertHolders.addAll(certHolders);
+ notifyDataSetChanged();
+ View content = mTabHost.getTabContentView();
+ mProgressBar.setVisibility(View.GONE);
+ mList.setVisibility(View.VISIBLE);
+ mProgressBar.setProgress(0);
+ }
+ }
+ }
+
+ private static class CertHolder implements Comparable<CertHolder> {
+ private final TrustedCertificateStore mStore;
+ private final TrustedCertificateAdapter mAdapter;
+ private final Tab mTab;
+ private final String mAlias;
+ private final X509Certificate mX509Cert;
+
+ private final SslCertificate mSslCert;
+ private final String mSubjectPrimary;
+ private final String mSubjectSecondary;
+ private boolean mDeleted;
+
+ private CertHolder(TrustedCertificateStore store,
+ TrustedCertificateAdapter adapter,
+ Tab tab,
+ String alias,
+ X509Certificate x509Cert) {
+ mStore = store;
+ mAdapter = adapter;
+ mTab = tab;
+ mAlias = alias;
+ mX509Cert = x509Cert;
+
+ mSslCert = new SslCertificate(x509Cert);
+
+ String cn = mSslCert.getIssuedTo().getCName();
+ String o = mSslCert.getIssuedTo().getOName();
+ String ou = mSslCert.getIssuedTo().getUName();
+ // if we have a O, use O as primary subject, secondary prefer CN over OU
+ // if we don't have an O, use CN as primary, empty secondary
+ // if we don't have O or CN, use DName as primary, empty secondary
+ if (!o.isEmpty()) {
+ if (!cn.isEmpty()) {
+ mSubjectPrimary = o;
+ mSubjectSecondary = cn;
+ } else {
+ mSubjectPrimary = o;
+ mSubjectSecondary = ou;
+ }
+ } else {
+ if (!cn.isEmpty()) {
+ mSubjectPrimary = cn;
+ mSubjectSecondary = "";
+ } else {
+ mSubjectPrimary = mSslCert.getIssuedTo().getDName();
+ mSubjectSecondary = "";
+ }
+ }
+ mDeleted = mTab.deleted(mStore, mAlias);
+ }
+ @Override public int compareTo(CertHolder o) {
+ int primary = this.mSubjectPrimary.compareToIgnoreCase(o.mSubjectPrimary);
+ if (primary != 0) {
+ return primary;
+ }
+ return this.mSubjectSecondary.compareToIgnoreCase(o.mSubjectSecondary);
+ }
+ @Override public boolean equals(Object o) {
+ if (!(o instanceof CertHolder)) {
+ return false;
+ }
+ CertHolder other = (CertHolder) o;
+ return mAlias.equals(other.mAlias);
+ }
+ @Override public int hashCode() {
+ return mAlias.hashCode();
+ }
+ }
+
+ private static class ViewHolder {
+ private TextView mSubjectPrimaryView;
+ private TextView mSubjectSecondaryView;
+ private CheckBox mCheckBox;
+ }
+
+ private void showCertDialog(final CertHolder certHolder) {
+ View view = certHolder.mSslCert.inflateCertificateView(getActivity());
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(com.android.internal.R.string.ssl_certificate);
+ builder.setView(view);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ }
+ });
+ final Dialog certDialog = builder.create();
+
+ ViewGroup body = (ViewGroup) view.findViewById(com.android.internal.R.id.body);
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ Button removeButton = (Button) inflater.inflate(R.layout.trusted_credential_details,
+ body,
+ false);
+ body.addView(removeButton);
+ removeButton.setText(certHolder.mTab.getButtonLabel(certHolder));
+ removeButton.setOnClickListener(new View.OnClickListener() {
+ @Override public void onClick(View v) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage(certHolder.mTab.getButtonConfirmation(certHolder));
+ builder.setPositiveButton(
+ android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override public void onClick(DialogInterface dialog, int id) {
+ new AliasOperation(certHolder).execute();
+ dialog.dismiss();
+ certDialog.dismiss();
+ }
+ });
+ builder.setNegativeButton(
+ android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+ });
+
+ certDialog.show();
+ }
+
+ private class AliasOperation extends AsyncTask<Void, Void, Boolean> {
+ private final CertHolder mCertHolder;
+ private AliasOperation(CertHolder certHolder) {
+ mCertHolder = certHolder;
+ }
+ @Override protected Boolean doInBackground(Void... params) {
+ try {
+ KeyChainConnection keyChainConnection = KeyChain.bind(getActivity());
+ IKeyChainService service = keyChainConnection.getService();
+ try {
+ if (mCertHolder.mDeleted) {
+ byte[] bytes = mCertHolder.mX509Cert.getEncoded();
+ service.installCaCertificate(bytes);
+ return true;
+ } else {
+ return service.deleteCaCertificate(mCertHolder.mAlias);
+ }
+ } finally {
+ keyChainConnection.close();
+ }
+ } catch (CertificateEncodingException e) {
+ return false;
+ } catch (IllegalStateException e) {
+ // used by installCaCertificate to report errors
+ return false;
+ } catch (RemoteException e) {
+ return false;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+ @Override protected void onPostExecute(Boolean ok) {
+ mCertHolder.mTab.postOperationUpdate(ok, mCertHolder);
+ }
+ }
+}
diff --git a/src/com/android/settings/UserDictionarySettings.java b/src/com/android/settings/UserDictionarySettings.java
index f712cb6..fa4359c 100644
--- a/src/com/android/settings/UserDictionarySettings.java
+++ b/src/com/android/settings/UserDictionarySettings.java
@@ -63,23 +63,29 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
// Either the locale is empty (means the word is applicable to all locales)
// or the word equals our current locale
- private static final String QUERY_SELECTION = UserDictionary.Words.LOCALE + "=? OR "
- + UserDictionary.Words.LOCALE + " is null";
+ private static final String QUERY_SELECTION =
+ UserDictionary.Words.LOCALE + "=?";
+ private static final String QUERY_SELECTION_ALL_LOCALES =
+ UserDictionary.Words.LOCALE + " is null";
private static final String DELETE_SELECTION = UserDictionary.Words.WORD + "=?";
private static final String EXTRA_WORD = "word";
-
+
private static final int OPTIONS_MENU_ADD = Menu.FIRST;
private static final int DIALOG_ADD_OR_EDIT = 0;
-
+
+ private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250;
+
/** The word being edited in the dialog (null means the user is adding a word). */
private String mDialogEditingWord;
private View mView;
private Cursor mCursor;
-
+
+ protected String mLocale;
+
private boolean mAddedWordAlready;
private boolean mAutoReturn;
@@ -93,7 +99,7 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- mView = inflater.inflate(R.layout.list_content_with_empty_view, container, false);
+ mView = inflater.inflate(R.layout.custom_preference_list_fragment, container, false);
return mView;
}
@@ -101,7 +107,25 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- mCursor = createCursor();
+ final Intent intent = getActivity().getIntent();
+ final String localeFromIntent =
+ null == intent ? null : intent.getStringExtra("locale");
+
+ final Bundle arguments = getArguments();
+ final String localeFromArguments =
+ null == arguments ? null : arguments.getString("locale");
+
+ final String locale;
+ if (null != localeFromArguments) {
+ locale = localeFromArguments;
+ } else if (null != localeFromIntent) {
+ locale = localeFromIntent;
+ } else {
+ locale = null;
+ }
+
+ mLocale = locale;
+ mCursor = createCursor(locale);
TextView emptyView = (TextView)mView.findViewById(R.id.empty);
emptyView.setText(R.string.user_dict_settings_empty_text);
@@ -117,12 +141,12 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
mAddedWordAlready = savedInstanceState.getBoolean(INSTANCE_KEY_ADDED_WORD, false);
}
}
-
+
@Override
public void onResume() {
super.onResume();
final Intent intent = getActivity().getIntent();
- if (!mAddedWordAlready
+ if (!mAddedWordAlready
&& intent.getAction().equals("com.android.settings.USER_DICTIONARY_INSERT")) {
final String word = intent.getStringExtra(EXTRA_WORD);
mAutoReturn = true;
@@ -139,12 +163,28 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
outState.putBoolean(INSTANCE_KEY_ADDED_WORD, mAddedWordAlready);
}
- private Cursor createCursor() {
- String currentLocale = Locale.getDefault().toString();
- // Case-insensitive sort
- return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
- QUERY_SELECTION, new String[] { currentLocale },
- "UPPER(" + UserDictionary.Words.WORD + ")");
+ private Cursor createCursor(final String locale) {
+ // Locale can be any of:
+ // - The string representation of a locale, as returned by Locale#toString()
+ // - The empty string. This means we want a cursor returning words valid for all locales.
+ // - null. This means we want a cursor for the current locale, whatever this is.
+ // Note that this contrasts with the data inside the database, where NULL means "all
+ // locales" and there should never be an empty string. The confusion is called by the
+ // historical use of null for "all locales".
+ // TODO: it should be easy to make this more readable by making the special values
+ // human-readable, like "all_locales" and "current_locales" strings, provided they
+ // can be guaranteed not to match locales that may exist.
+ if ("".equals(locale)) {
+ // Case-insensitive sort
+ return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
+ QUERY_SELECTION_ALL_LOCALES, null,
+ "UPPER(" + UserDictionary.Words.WORD + ")");
+ } else {
+ final String queryLocale = null != locale ? locale : Locale.getDefault().toString();
+ return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
+ QUERY_SELECTION, new String[] { queryLocale },
+ "UPPER(" + UserDictionary.Words.WORD + ")");
+ }
}
private ListAdapter createAdapter() {
@@ -153,7 +193,7 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
new String[] { UserDictionary.Words.WORD, UserDictionary.Words._ID },
new int[] { android.R.id.text1, R.id.delete_button }, this);
}
-
+
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
String word = getWord(position);
@@ -167,7 +207,8 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
MenuItem actionItem =
menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title)
.setIcon(R.drawable.ic_menu_add);
- actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
+ MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
@Override
@@ -182,6 +223,7 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
}
private String getWord(int position) {
+ if (null == mCursor) return null;
mCursor.moveToPosition(position);
// Handle a possible race-condition
if (mCursor.isAfterLast()) return null;
@@ -235,14 +277,29 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
// The user was editing a word, so do a delete/add
deleteWord(mDialogEditingWord);
}
-
+
// Disallow duplicates
deleteWord(word);
-
+
// TODO: present UI for picking whether to add word to all locales, or current.
- UserDictionary.Words.addWord(getActivity(), word.toString(),
- 250, UserDictionary.Words.LOCALE_TYPE_ALL);
- if (!mCursor.requery()) {
+ if (null == mLocale) {
+ // Null means insert with the default system locale.
+ UserDictionary.Words.addWord(getActivity(), word.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT);
+ } else if ("".equals(mLocale)) {
+ // Empty string means insert for all languages.
+ UserDictionary.Words.addWord(getActivity(), word.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_ALL);
+ } else {
+ // TODO: fix the framework so that it can accept a locale when we add a word
+ // to the user dictionary instead of querying the system locale.
+ final Locale prevLocale = Locale.getDefault();
+ Locale.setDefault(Utils.createLocaleFromString(mLocale));
+ UserDictionary.Words.addWord(getActivity(), word.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT);
+ Locale.setDefault(prevLocale);
+ }
+ if (null != mCursor && !mCursor.requery()) {
throw new IllegalStateException("can't requery on already-closed cursor.");
}
mAddedWordAlready = true;
@@ -277,23 +334,25 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
super(context, layout, c, from, to);
mSettings = settings;
- int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
- String alphabet = context.getString(
- com.android.internal.R.string.fast_scroll_alphabet);
- mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet);
+ if (null != c) {
+ final String alphabet = context.getString(
+ com.android.internal.R.string.fast_scroll_alphabet);
+ final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
+ mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet);
+ }
setViewBinder(mViewBinder);
}
public int getPositionForSection(int section) {
- return mIndexer.getPositionForSection(section);
+ return null == mIndexer ? 0 : mIndexer.getPositionForSection(section);
}
public int getSectionForPosition(int position) {
- return mIndexer.getSectionForPosition(position);
+ return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position);
}
public Object[] getSections() {
- return mIndexer.getSections();
+ return null == mIndexer ? null : mIndexer.getSections();
}
public void onClick(View v) {
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 18c6159..b725d56 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -27,6 +27,7 @@ import android.content.res.Resources.NotFoundException;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
+import android.os.BatteryManager;
import android.os.Bundle;
import android.os.SystemProperties;
import android.preference.Preference;
@@ -36,8 +37,10 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import java.net.InetAddress;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
public class Utils {
@@ -308,4 +311,58 @@ public class Utils {
}
return addresses;
}
+
+ public static Locale createLocaleFromString(String localeStr) {
+ // TODO: is there a better way to actually construct a locale that will match?
+ // The main problem is, on top of Java specs, locale.toString() and
+ // new Locale(locale.toString()).toString() do not return equal() strings in
+ // many cases, because the constructor takes the only string as the language
+ // code. So : new Locale("en", "US").toString() => "en_US"
+ // And : new Locale("en_US").toString() => "en_us"
+ if (null == localeStr)
+ return Locale.getDefault();
+ String[] brokenDownLocale = localeStr.split("_", 3);
+ // split may not return a 0-length array.
+ if (1 == brokenDownLocale.length) {
+ return new Locale(brokenDownLocale[0]);
+ } else if (2 == brokenDownLocale.length) {
+ return new Locale(brokenDownLocale[0], brokenDownLocale[1]);
+ } else {
+ return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]);
+ }
+ }
+
+ public static String getBatteryPercentage(Intent batteryChangedIntent) {
+ int level = batteryChangedIntent.getIntExtra("level", 0);
+ int scale = batteryChangedIntent.getIntExtra("scale", 100);
+ return String.valueOf(level * 100 / scale) + "%";
+ }
+
+ public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
+ final Intent intent = batteryChangedIntent;
+
+ int plugType = intent.getIntExtra("plugged", 0);
+ int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
+ String statusString;
+ if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+ statusString = res.getString(R.string.battery_info_status_charging);
+ if (plugType > 0) {
+ statusString = statusString
+ + " "
+ + res.getString((plugType == BatteryManager.BATTERY_PLUGGED_AC)
+ ? R.string.battery_info_status_charging_ac
+ : R.string.battery_info_status_charging_usb);
+ }
+ } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ statusString = res.getString(R.string.battery_info_status_discharging);
+ } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+ statusString = res.getString(R.string.battery_info_status_not_charging);
+ } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
+ statusString = res.getString(R.string.battery_info_status_full);
+ } else {
+ statusString = res.getString(R.string.battery_info_status_unknown);
+ }
+
+ return statusString;
+ }
}
diff --git a/src/com/android/settings/WallpaperTypeSettings.java b/src/com/android/settings/WallpaperTypeSettings.java
new file mode 100644
index 0000000..fa5f0ac
--- /dev/null
+++ b/src/com/android/settings/WallpaperTypeSettings.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+
+import java.util.List;
+
+public class WallpaperTypeSettings extends SettingsPreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.wallpaper_settings);
+ populateWallpaperTypes();
+ }
+
+ private void populateWallpaperTypes() {
+ // Search for activities that satisfy the ACTION_SET_WALLPAPER action
+ Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ final PackageManager pm = getPackageManager();
+ List<ResolveInfo> rList = pm.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+
+ final PreferenceScreen parent = getPreferenceScreen();
+ parent.setOrderingAsAdded(false);
+ // Add Preference items for each of the matching activities
+ for (ResolveInfo info : rList) {
+ Preference pref = new Preference(getActivity());
+ Intent prefIntent = new Intent(intent);
+ prefIntent.setComponent(new ComponentName(
+ info.activityInfo.packageName, info.activityInfo.name));
+ pref.setIntent(prefIntent);
+ CharSequence label = info.loadLabel(pm);
+ if (label == null) label = info.activityInfo.packageName;
+ pref.setTitle(label);
+ parent.addPreference(pref);
+ }
+ }
+}
diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java
index 2844f3b..636799d 100644
--- a/src/com/android/settings/WirelessSettings.java
+++ b/src/com/android/settings/WirelessSettings.java
@@ -16,36 +16,34 @@
package com.android.settings;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.settings.bluetooth.BluetoothEnabler;
-import com.android.settings.wifi.WifiEnabler;
-import com.android.settings.nfc.NfcEnabler;
-
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
-import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
+import android.net.wifi.p2p.WifiP2pManager;
import android.nfc.NfcAdapter;
import android.os.Bundle;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Switch;
+
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.settings.nfc.NfcEnabler;
public class WirelessSettings extends SettingsPreferenceFragment {
private static final String KEY_TOGGLE_AIRPLANE = "toggle_airplane";
- private static final String KEY_TOGGLE_BLUETOOTH = "toggle_bluetooth";
- private static final String KEY_TOGGLE_WIFI = "toggle_wifi";
private static final String KEY_TOGGLE_NFC = "toggle_nfc";
- private static final String KEY_WIFI_SETTINGS = "wifi_settings";
- private static final String KEY_BT_SETTINGS = "bt_settings";
+ private static final String KEY_ZEROCLICK_SETTINGS = "zeroclick_settings";
private static final String KEY_VPN_SETTINGS = "vpn_settings";
+ private static final String KEY_WIFI_P2P_SETTINGS = "wifi_p2p_settings";
private static final String KEY_TETHER_SETTINGS = "tether_settings";
private static final String KEY_PROXY_SETTINGS = "proxy_settings";
private static final String KEY_MOBILE_NETWORK_SETTINGS = "mobile_network_settings";
@@ -55,9 +53,8 @@ public class WirelessSettings extends SettingsPreferenceFragment {
private AirplaneModeEnabler mAirplaneModeEnabler;
private CheckBoxPreference mAirplaneModePreference;
- private WifiEnabler mWifiEnabler;
private NfcEnabler mNfcEnabler;
- private BluetoothEnabler mBtEnabler;
+ private NfcAdapter mNfcAdapter;
/**
* Invoked on each preference click in this hierarchy, overrides
@@ -95,42 +92,38 @@ public class WirelessSettings extends SettingsPreferenceFragment {
addPreferencesFromResource(R.xml.wireless_settings);
final Activity activity = getActivity();
- CheckBoxPreference airplane = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
- CheckBoxPreference wifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI);
- CheckBoxPreference bt = (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH);
+ mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
CheckBoxPreference nfc = (CheckBoxPreference) findPreference(KEY_TOGGLE_NFC);
+ PreferenceScreen zeroclick = (PreferenceScreen) findPreference(KEY_ZEROCLICK_SETTINGS);
- mAirplaneModeEnabler = new AirplaneModeEnabler(activity, airplane);
- mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
- mWifiEnabler = new WifiEnabler(activity, wifi);
- mBtEnabler = new BluetoothEnabler(activity, bt);
- mNfcEnabler = new NfcEnabler(activity, nfc);
+ mAirplaneModeEnabler = new AirplaneModeEnabler(activity, mAirplaneModePreference);
+ mNfcEnabler = new NfcEnabler(activity, nfc, zeroclick);
String toggleable = Settings.System.getString(activity.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);
+ // No bluetooth-dependent items in the list. Code kept in case one is added later.
}
- // Remove Bluetooth Settings if Bluetooth service is not available.
- if (ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE) == null) {
- getPreferenceScreen().removePreference(bt);
- getPreferenceScreen().removePreference(findPreference(KEY_BT_SETTINGS));
+ // Manually set dependencies for NFC when not toggleable.
+ if (toggleable == null || !toggleable.contains(Settings.System.RADIO_NFC)) {
+ findPreference(KEY_TOGGLE_NFC).setDependency(KEY_TOGGLE_AIRPLANE);
+ findPreference(KEY_ZEROCLICK_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE);
}
// Remove NFC if its not available
- if (NfcAdapter.getDefaultAdapter(activity) == null) {
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(activity);
+ if (mNfcAdapter == null) {
getPreferenceScreen().removePreference(nfc);
+ getPreferenceScreen().removePreference(zeroclick);
+ mNfcEnabler = null;
}
// Remove Mobile Network Settings if it's a wifi-only device.
@@ -138,6 +131,11 @@ public class WirelessSettings extends SettingsPreferenceFragment {
getPreferenceScreen().removePreference(findPreference(KEY_MOBILE_NETWORK_SETTINGS));
}
+ WifiP2pManager wifiP2p = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
+ if (!wifiP2p.isP2pSupported()) {
+ getPreferenceScreen().removePreference(findPreference(KEY_WIFI_P2P_SETTINGS));
+ }
+
// Enable Proxy selector settings if allowed.
Preference mGlobalProxy = findPreference(KEY_PROXY_SETTINGS);
DevicePolicyManager mDPM = (DevicePolicyManager)
@@ -163,25 +161,18 @@ public class WirelessSettings extends SettingsPreferenceFragment {
Preference p = findPreference(KEY_TETHER_SETTINGS);
if (wifiAvailable && usbAvailable && bluetoothAvailable) {
p.setTitle(R.string.tether_settings_title_all);
- p.setSummary(R.string.tether_settings_summary_all);
} else if (wifiAvailable && usbAvailable) {
p.setTitle(R.string.tether_settings_title_all);
- p.setSummary(R.string.tether_settings_summary_usb_wifi);
} else if (wifiAvailable && bluetoothAvailable) {
p.setTitle(R.string.tether_settings_title_all);
- p.setSummary(R.string.tether_settings_summary_wifi_bluetooth);
} else if (wifiAvailable) {
p.setTitle(R.string.tether_settings_title_wifi);
- p.setSummary(R.string.tether_settings_summary_wifi);
} else if (usbAvailable && bluetoothAvailable) {
p.setTitle(R.string.tether_settings_title_usb_bluetooth);
- p.setSummary(R.string.tether_settings_summary_usb_bluetooth);
} else if (usbAvailable) {
p.setTitle(R.string.tether_settings_title_usb);
- p.setSummary(R.string.tether_settings_summary_usb);
} else {
p.setTitle(R.string.tether_settings_title_bluetooth);
- p.setSummary(R.string.tether_settings_summary_bluetooth);
}
}
}
@@ -191,9 +182,9 @@ public class WirelessSettings extends SettingsPreferenceFragment {
super.onResume();
mAirplaneModeEnabler.resume();
- mWifiEnabler.resume();
- mBtEnabler.resume();
- mNfcEnabler.resume();
+ if (mNfcEnabler != null) {
+ mNfcEnabler.resume();
+ }
}
@Override
@@ -201,9 +192,9 @@ public class WirelessSettings extends SettingsPreferenceFragment {
super.onPause();
mAirplaneModeEnabler.pause();
- mWifiEnabler.pause();
- mBtEnabler.pause();
- mNfcEnabler.pause();
+ if (mNfcEnabler != null) {
+ mNfcEnabler.pause();
+ }
}
@Override
diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java
index a84bece..a0d6a7f 100644
--- a/src/com/android/settings/accounts/AccountPreferenceBase.java
+++ b/src/com/android/settings/accounts/AccountPreferenceBase.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.SyncAdapterType;
import android.content.SyncStatusObserver;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
@@ -113,7 +114,7 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
mAccountTypeToAuthorities.put(sa.accountType, authorities);
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "added authority " + sa.authority + " to accountType "
+ Log.d(TAG, "added authority " + sa.authority + " to accountType "
+ sa.accountType);
}
authorities.add(sa.authority);
@@ -136,7 +137,10 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
icon = authContext.getResources().getDrawable(desc.iconId);
} catch (PackageManager.NameNotFoundException e) {
// TODO: place holder icon for missing account icons?
- Log.w(TAG, "No icon for account type " + accountType);
+ Log.w(TAG, "No icon name for account type " + accountType);
+ } catch (Resources.NotFoundException e) {
+ // TODO: place holder icon for missing account icons?
+ Log.w(TAG, "No icon resource for account type " + accountType);
}
}
return icon;
@@ -155,7 +159,9 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
Context authContext = getActivity().createPackageContext(desc.packageName, 0);
label = authContext.getResources().getText(desc.labelId);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "No label for account type " + ", type " + accountType);
+ Log.w(TAG, "No label name for account type " + accountType);
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "No label icon for account type " + accountType);
}
}
return label;
@@ -179,6 +185,8 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
}
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName);
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName);
}
}
return prefs;
diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java
index 9ef0481..e70cbad 100644
--- a/src/com/android/settings/accounts/AccountSyncSettings.java
+++ b/src/com/android/settings/accounts/AccountSyncSettings.java
@@ -16,10 +16,6 @@
package com.android.settings.accounts;
-import com.android.settings.R;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
@@ -53,6 +49,10 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.settings.R;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -175,7 +175,13 @@ public class AccountSyncSettings extends AccountPreferenceBase {
mDateFormat = DateFormat.getDateFormat(activity);
mTimeFormat = DateFormat.getTimeFormat(activity);
- mAccount = (Account) getArguments().getParcelable(ACCOUNT_KEY);
+ Bundle arguments = getArguments();
+ if (arguments == null) {
+ Log.e(TAG, "No arguments provided when starting intent. ACCOUNT_KEY needed.");
+ return;
+ }
+
+ mAccount = (Account) arguments.getParcelable(ACCOUNT_KEY);
if (mAccount != null) {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Got account: " + mAccount);
mUserId.setText(mAccount.name);
@@ -230,10 +236,12 @@ public class AccountSyncSettings extends AccountPreferenceBase {
getString(R.string.sync_menu_sync_cancel))
.setIcon(com.android.internal.R.drawable.ic_menu_close_clear_cancel);
- removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS
- | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ removeAccount.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
+ MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ syncNow.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
+ MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ syncCancel.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER |
+ MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
@Override
@@ -486,11 +494,13 @@ public class AccountSyncSettings extends AccountPreferenceBase {
protected void onAuthDescriptionsUpdated() {
super.onAuthDescriptionsUpdated();
getPreferenceScreen().removeAll();
- mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
- mProviderId.setText(getLabelForType(mAccount.type));
- PreferenceScreen prefs = addPreferencesForType(mAccount.type);
- if (prefs != null) {
- updatePreferenceIntents(prefs);
+ if (mAccount != null) {
+ mProviderIcon.setImageDrawable(getDrawableForType(mAccount.type));
+ mProviderId.setText(getLabelForType(mAccount.type));
+ PreferenceScreen prefs = addPreferencesForType(mAccount.type);
+ if (prefs != null) {
+ updatePreferenceIntents(prefs);
+ }
}
addPreferencesFromResource(R.xml.account_sync_settings);
}
diff --git a/src/com/android/settings/accounts/AddAccountSettings.java b/src/com/android/settings/accounts/AddAccountSettings.java
index 72ef130..382481e 100644
--- a/src/com/android/settings/accounts/AddAccountSettings.java
+++ b/src/com/android/settings/accounts/AddAccountSettings.java
@@ -22,6 +22,7 @@ import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
@@ -44,18 +45,37 @@ import java.io.IOException;
* when returning from each account setup, which doesn't look good.
*/
public class AddAccountSettings extends Activity {
+ /**
+ *
+ */
+ private static final String KEY_ADD_CALLED = "AddAccountCalled";
+
+ /**
+ * Extra parameter to identify the caller. Applications may display a
+ * different UI if the calls is made from Settings or from a specific
+ * application.
+ */
+ private static final String KEY_CALLER_IDENTITY = "pendingIntent";
+
private static final String TAG = "AccountSettings";
/* package */ static final String EXTRA_SELECTED_ACCOUNT = "selected_account";
private static final int CHOOSE_ACCOUNT_REQUEST = 1;
+ private PendingIntent mPendingIntent;
+
private AccountManagerCallback<Bundle> mCallback = new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle bundle = future.getResult();
bundle.keySet();
setResult(RESULT_OK);
+
+ if (mPendingIntent != null) {
+ mPendingIntent.cancel();
+ }
+
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "account added: " + bundle);
} catch (OperationCanceledException e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount was canceled");
@@ -69,10 +89,22 @@ public class AddAccountSettings extends Activity {
}
};
+ private boolean mAddAccountCalled = false;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mAddAccountCalled = savedInstanceState.getBoolean(KEY_ADD_CALLED);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "restored");
+ }
+
+ if (mAddAccountCalled) {
+ // We already called add account - maybe the callback was lost.
+ finish();
+ return;
+ }
final String[] authorities =
getIntent().getStringArrayExtra(AccountPreferenceBase.AUTHORITIES_FILTER_KEY);
final String[] accountTypes =
@@ -102,14 +134,24 @@ public class AddAccountSettings extends Activity {
}
}
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_ADD_CALLED, mAddAccountCalled);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "saved");
+ }
+
private void addAccount(String accountType) {
+ Bundle addAccountOptions = new Bundle();
+ mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);
+ addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
AccountManager.get(this).addAccount(
accountType,
null, /* authTokenType */
null, /* requiredFeatures */
- null, /* addAccountOptions */
+ addAccountOptions,
this,
mCallback,
null /* handler */);
+ mAddAccountCalled = true;
}
}
diff --git a/src/com/android/settings/accounts/ChooseAccountActivity.java b/src/com/android/settings/accounts/ChooseAccountActivity.java
index 9576dee..631fe47 100644
--- a/src/com/android/settings/accounts/ChooseAccountActivity.java
+++ b/src/com/android/settings/accounts/ChooseAccountActivity.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SyncAdapterType;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.Preference;
@@ -199,7 +200,10 @@ public class ChooseAccountActivity extends PreferenceActivity {
icon = authContext.getResources().getDrawable(desc.iconId);
} catch (PackageManager.NameNotFoundException e) {
// TODO: place holder icon for missing account icons?
- Log.w(TAG, "No icon for account type " + accountType);
+ Log.w(TAG, "No icon name for account type " + accountType);
+ } catch (Resources.NotFoundException e) {
+ // TODO: place holder icon for missing account icons?
+ Log.w(TAG, "No icon resource for account type " + accountType);
}
}
return icon;
@@ -218,7 +222,9 @@ public class ChooseAccountActivity extends PreferenceActivity {
Context authContext = createPackageContext(desc.packageName, 0);
label = authContext.getResources().getText(desc.labelId);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "No label for account type " + ", type " + accountType);
+ Log.w(TAG, "No label name for account type " + accountType);
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "No label resource for account type " + accountType);
}
}
return label;
diff --git a/src/com/android/settings/accounts/ManageAccountsSettings.java b/src/com/android/settings/accounts/ManageAccountsSettings.java
index 06c5ff0..d9dea00 100644
--- a/src/com/android/settings/accounts/ManageAccountsSettings.java
+++ b/src/com/android/settings/accounts/ManageAccountsSettings.java
@@ -19,8 +19,6 @@ package com.android.settings.accounts;
import com.android.settings.AccountPreference;
import com.android.settings.DialogCreatable;
import com.android.settings.R;
-import com.android.settings.vpn.VpnTypeSelection;
-import com.google.android.collect.Maps;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -36,7 +34,6 @@ import android.content.Intent;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
import android.content.SyncStatusInfo;
-import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.os.Bundle;
@@ -202,6 +199,7 @@ public class ManageAccountsSettings extends AccountPreferenceBase
return null;
}
+ @Override
public void showDialog(int dialogId) {
if (mDialogFragment != null) {
Log.e(TAG, "Old dialog fragment not null!");
@@ -213,8 +211,7 @@ public class ManageAccountsSettings extends AccountPreferenceBase
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
MenuItem actionItem =
- menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_account_label)
- .setIcon(R.drawable.ic_menu_add);
+ menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_account_label);
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
| MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
@@ -236,6 +233,7 @@ public class ManageAccountsSettings extends AccountPreferenceBase
connManager.setBackgroundDataSetting(enabled);
}
+ @Override
protected void onSyncStateUpdated() {
// Catch any delayed delivery of update messages
if (getActivity() == null) return;
@@ -343,6 +341,7 @@ public class ManageAccountsSettings extends AccountPreferenceBase
onSyncStateUpdated();
}
+ @Override
protected void onAuthDescriptionsUpdated() {
// Update account icons for all account preference items
for (int i = 0; i < mManageAccountsCategory.getPreferenceCount(); i++) {
diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java
index 11e4aae..e0899cb 100644
--- a/src/com/android/settings/applications/ApplicationsState.java
+++ b/src/com/android/settings/applications/ApplicationsState.java
@@ -5,14 +5,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
@@ -74,7 +71,8 @@ public class ApplicationsState {
long cacheSize;
long codeSize;
long dataSize;
- long externalSize;
+ long externalCodeSize;
+ long externalDataSize;
}
public static class AppEntry extends SizeInfo {
@@ -82,6 +80,8 @@ public class ApplicationsState {
final long id;
String label;
long size;
+ long internalSize;
+ long externalSize;
boolean mounted;
@@ -97,6 +97,8 @@ public class ApplicationsState {
ApplicationInfo info;
Drawable icon;
String sizeStr;
+ String internalSizeStr;
+ String externalSizeStr;
boolean sizeStale;
long sizeLoadStart;
@@ -155,7 +157,8 @@ public class ApplicationsState {
}
};
- public static final Comparator<AppEntry> SIZE_COMPARATOR = new Comparator<AppEntry>() {
+ public static final Comparator<AppEntry> SIZE_COMPARATOR
+ = new Comparator<AppEntry>() {
private final Collator sCollator = Collator.getInstance();
@Override
public int compare(AppEntry object1, AppEntry object2) {
@@ -165,6 +168,28 @@ public class ApplicationsState {
}
};
+ public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
+ = new Comparator<AppEntry>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppEntry object1, AppEntry object2) {
+ if (object1.internalSize < object2.internalSize) return 1;
+ if (object1.internalSize > object2.internalSize) return -1;
+ return sCollator.compare(object1.label, object2.label);
+ }
+ };
+
+ public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
+ = new Comparator<AppEntry>() {
+ private final Collator sCollator = Collator.getInstance();
+ @Override
+ public int compare(AppEntry object1, AppEntry object2) {
+ if (object1.externalSize < object2.externalSize) return 1;
+ if (object1.externalSize > object2.externalSize) return -1;
+ return sCollator.compare(object1.label, object2.label);
+ }
+ };
+
public static final AppFilter THIRD_PARTY_FILTER = new AppFilter() {
public void init() {
}
@@ -392,6 +417,14 @@ public class ApplicationsState {
for (int i=0; i<mApplications.size(); i++) {
final ApplicationInfo info = mApplications.get(i);
+ // Need to trim out any applications that are disabled by
+ // something different than the user.
+ if (!info.enabled && info.enabledSetting
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ mApplications.remove(i);
+ i--;
+ continue;
+ }
final AppEntry entry = mEntriesMap.get(info.packageName);
if (entry != null) {
entry.info = info;
@@ -652,8 +685,8 @@ public class ApplicationsState {
private long getTotalExternalSize(PackageStats ps) {
if (ps != null) {
- return ps.externalDataSize + ps.externalMediaSize + ps.externalCacheSize
- + ps.externalObbSize;
+ return ps.externalCodeSize + ps.externalDataSize
+ + ps.externalMediaSize + ps.externalObbSize;
}
return SIZE_INVALID;
}
@@ -685,19 +718,29 @@ public class ApplicationsState {
synchronized (entry) {
entry.sizeStale = false;
entry.sizeLoadStart = 0;
- long externalSize = getTotalExternalSize(stats);
- long newSize = externalSize + getTotalInternalSize(stats);
+ long externalCodeSize = stats.externalCodeSize
+ + stats.externalObbSize;
+ long externalDataSize = stats.externalDataSize
+ + stats.externalMediaSize + stats.externalCacheSize;
+ long newSize = externalCodeSize + externalDataSize
+ + getTotalInternalSize(stats);
if (entry.size != newSize ||
entry.cacheSize != stats.cacheSize ||
entry.codeSize != stats.codeSize ||
entry.dataSize != stats.dataSize ||
- entry.externalSize != externalSize) {
+ entry.externalCodeSize != externalCodeSize ||
+ entry.externalDataSize != externalDataSize) {
entry.size = newSize;
entry.cacheSize = stats.cacheSize;
entry.codeSize = stats.codeSize;
entry.dataSize = stats.dataSize;
- entry.externalSize = externalSize;
+ entry.externalCodeSize = externalCodeSize;
+ entry.externalDataSize = externalDataSize;
entry.sizeStr = getSizeStr(entry.size);
+ entry.internalSize = getTotalInternalSize(stats);
+ entry.internalSizeStr = getSizeStr(entry.internalSize);
+ entry.externalSize = getTotalExternalSize(stats);
+ entry.externalSizeStr = getSizeStr(entry.externalSize);
if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
+ ": " + entry.sizeStr);
sizeChanged = true;
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 629bac5..a84b8bb 100644
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -79,7 +79,7 @@ public class InstalledAppDetails extends Fragment
implements View.OnClickListener, CompoundButton.OnCheckedChangeListener,
ApplicationsState.Callbacks {
private static final String TAG="InstalledAppDetails";
- static final boolean SUPPORT_DISABLE_APPS = false;
+ static final boolean SUPPORT_DISABLE_APPS = true;
private static final boolean localLOGV = false;
public static final String ARG_PACKAGE_NAME = "package";
@@ -103,7 +103,8 @@ public class InstalledAppDetails extends Fragment
private TextView mTotalSize;
private TextView mAppSize;
private TextView mDataSize;
- private TextView mExternalSize;
+ private TextView mExternalCodeSize;
+ private TextView mExternalDataSize;
private ClearUserDataObserver mClearDataObserver;
// Views related to cache info
private TextView mCacheSize;
@@ -118,7 +119,8 @@ public class InstalledAppDetails extends Fragment
private boolean mHaveSizes = false;
private long mLastCodeSize = -1;
private long mLastDataSize = -1;
- private long mLastExternalSize = -1;
+ private long mLastExternalCodeSize = -1;
+ private long mLastExternalDataSize = -1;
private long mLastCacheSize = -1;
private long mLastTotalSize = -1;
@@ -145,6 +147,7 @@ public class InstalledAppDetails extends Fragment
private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 4;
private static final int DLG_FORCE_STOP = DLG_BASE + 5;
private static final int DLG_MOVE_FAILED = DLG_BASE + 6;
+ private static final int DLG_DISABLE = DLG_BASE + 7;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
@@ -282,7 +285,7 @@ public class InstalledAppDetails extends Fragment
intent.setPackage(mAppEntry.info.packageName);
List<ResolveInfo> homes = mPm.queryIntentActivities(intent, 0);
if ((homes != null && homes.size() > 0) ||
- (mPackageInfo != null &&
+ (mPackageInfo != null && mPackageInfo.signatures != null &&
sys.signatures[0].equals(mPackageInfo.signatures[0]))) {
// Disable button for core system applications.
mUninstallButton.setText(R.string.disable_text);
@@ -331,7 +334,8 @@ public class InstalledAppDetails extends Fragment
mTotalSize = (TextView)view.findViewById(R.id.total_size_text);
mAppSize = (TextView)view.findViewById(R.id.application_size_text);
mDataSize = (TextView)view.findViewById(R.id.data_size_text);
- mExternalSize = (TextView)view.findViewById(R.id.external_size_text);
+ mExternalCodeSize = (TextView)view.findViewById(R.id.external_code_size_text);
+ mExternalDataSize = (TextView)view.findViewById(R.id.external_data_size_text);
// Get Control button panel
View btnPanel = view.findViewById(R.id.control_buttons_panel);
@@ -547,9 +551,13 @@ public class InstalledAppDetails extends Fragment
mLastDataSize = mAppEntry.dataSize;
mDataSize.setText(getSizeStr(mAppEntry.dataSize));
}
- if (mLastExternalSize != mAppEntry.externalSize) {
- mLastExternalSize = mAppEntry.externalSize;
- mExternalSize.setText(getSizeStr(mAppEntry.externalSize));
+ if (mLastExternalCodeSize != mAppEntry.externalCodeSize) {
+ mLastExternalCodeSize = mAppEntry.externalCodeSize;
+ mExternalCodeSize.setText(getSizeStr(mAppEntry.externalCodeSize));
+ }
+ if (mLastExternalDataSize != mAppEntry.externalDataSize) {
+ mLastExternalDataSize = mAppEntry.externalDataSize;
+ mExternalDataSize.setText(getSizeStr(mAppEntry.externalDataSize));
}
if (mLastCacheSize != mAppEntry.cacheSize) {
mLastCacheSize = mAppEntry.cacheSize;
@@ -747,6 +755,22 @@ public class InstalledAppDetails extends Fragment
.setMessage(msg)
.setNeutralButton(R.string.dlg_ok, null)
.create();
+ case DLG_DISABLE:
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(getActivity().getText(R.string.app_disable_dlg_title))
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
+ .setPositiveButton(R.string.dlg_ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Disable the app
+ new DisableChanger(getOwner(), getOwner().mAppEntry.info,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
+ .execute((Object)null);
+ }
+ })
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
}
throw new IllegalArgumentException("unknown id " + id);
}
@@ -830,9 +854,13 @@ public class InstalledAppDetails extends Fragment
showDialogInner(DLG_FACTORY_RESET, 0);
} else {
if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- new DisableChanger(this, mAppEntry.info, mAppEntry.info.enabled ?
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).execute((Object)null);
+ if (mAppEntry.info.enabled) {
+ showDialogInner(DLG_DISABLE, 0);
+ } else {
+ new DisableChanger(this, mAppEntry.info,
+ PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+ .execute((Object)null);
+ }
} else {
uninstallPkg(packageName);
}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 85db45e..68c942d 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -121,6 +121,10 @@ public class ManageApplications extends Fragment implements
// constant value that can be used to check return code from sub activity.
private static final int INSTALLED_APP_DETAILS = 1;
+ public static final int SIZE_TOTAL = 0;
+ public static final int SIZE_INTERNAL = 1;
+ public static final int SIZE_EXTERNAL = 2;
+
// sort order that can be changed through the menu can be sorted alphabetically
// or size(descending)
private static final int MENU_OPTIONS_BASE = 0;
@@ -208,11 +212,21 @@ public class ManageApplications extends Fragment implements
TextView disabled;
CheckBox checkBox;
- void updateSizeText(ManageApplications ma) {
+ void updateSizeText(ManageApplications ma, int whichSize) {
if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry
+ ": " + entry.sizeStr);
if (entry.sizeStr != null) {
- appSize.setText(entry.sizeStr);
+ switch (whichSize) {
+ case SIZE_INTERNAL:
+ appSize.setText(entry.internalSizeStr);
+ break;
+ case SIZE_EXTERNAL:
+ appSize.setText(entry.externalSizeStr);
+ break;
+ default:
+ appSize.setText(entry.sizeStr);
+ break;
+ }
} else if (entry.size == ApplicationsState.SIZE_INVALID) {
appSize.setText(ma.mInvalidSizeStr);
}
@@ -237,6 +251,7 @@ public class ManageApplications extends Fragment implements
private boolean mResumed;
private int mLastFilterMode=-1, mLastSortMode=-1;
private boolean mWaitingForData;
+ private int mWhichSize = SIZE_TOTAL;
CharSequence mCurFilterPrefix;
private Filter mFilter = new Filter() {
@@ -296,12 +311,21 @@ public class ManageApplications extends Fragment implements
if (DEBUG) Log.i(TAG, "Rebuilding app list...");
ApplicationsState.AppFilter filterObj;
Comparator<AppEntry> comparatorObj;
+ boolean emulated = Environment.isExternalStorageEmulated();
+ if (emulated) {
+ mWhichSize = SIZE_TOTAL;
+ } else {
+ mWhichSize = SIZE_INTERNAL;
+ }
switch (mLastFilterMode) {
case FILTER_APPS_THIRD_PARTY:
filterObj = ApplicationsState.THIRD_PARTY_FILTER;
break;
case FILTER_APPS_SDCARD:
filterObj = ApplicationsState.ON_SD_CARD_FILTER;
+ if (!emulated) {
+ mWhichSize = SIZE_EXTERNAL;
+ }
break;
default:
filterObj = null;
@@ -309,7 +333,17 @@ public class ManageApplications extends Fragment implements
}
switch (mLastSortMode) {
case SORT_ORDER_SIZE:
- comparatorObj = ApplicationsState.SIZE_COMPARATOR;
+ switch (mWhichSize) {
+ case SIZE_INTERNAL:
+ comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR;
+ break;
+ case SIZE_EXTERNAL:
+ comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR;
+ break;
+ default:
+ comparatorObj = ApplicationsState.SIZE_COMPARATOR;
+ break;
+ }
break;
default:
comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
@@ -399,7 +433,7 @@ public class ManageApplications extends Fragment implements
AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
if (holder.entry.info.packageName.equals(packageName)) {
synchronized (holder.entry) {
- holder.updateSizeText(ManageApplications.this);
+ holder.updateSizeText(ManageApplications.this, mWhichSize);
}
if (holder.entry.info.packageName.equals(mCurrentPkgName)
&& mLastSortMode == SORT_ORDER_SIZE) {
@@ -478,7 +512,7 @@ public class ManageApplications extends Fragment implements
if (entry.icon != null) {
holder.appIcon.setImageDrawable(entry.icon);
}
- holder.updateSizeText(ManageApplications.this);
+ holder.updateSizeText(ManageApplications.this, mWhichSize);
if (InstalledAppDetails.SUPPORT_DISABLE_APPS) {
holder.disabled.setVisibility(entry.info.enabled ? View.GONE : View.VISIBLE);
} else {
@@ -777,6 +811,11 @@ public class ManageApplications extends Fragment implements
} catch (IllegalArgumentException e) {
// use the old value of mFreeMem
}
+ final int N = mApplicationsAdapter.getCount();
+ for (int i=0; i<N; i++) {
+ ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i);
+ appStorage += ae.externalCodeSize + ae.externalDataSize;
+ }
} else {
if (!mLastShowedInternalStorage) {
mLastShowedInternalStorage = true;
@@ -790,10 +829,14 @@ public class ManageApplications extends Fragment implements
mDataFileStats.getBlockSize();
} catch (IllegalArgumentException e) {
}
+ final boolean emulatedStorage = Environment.isExternalStorageEmulated();
final int N = mApplicationsAdapter.getCount();
for (int i=0; i<N; i++) {
ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i);
appStorage += ae.codeSize + ae.dataSize;
+ if (emulatedStorage) {
+ appStorage += ae.externalCodeSize + ae.externalDataSize;
+ }
}
freeStorage += mApplicationsState.sumCacheSizes();
}
diff --git a/src/com/android/settings/applications/RunningProcessesView.java b/src/com/android/settings/applications/RunningProcessesView.java
index 1f91c33..7c3ebb0 100644
--- a/src/com/android/settings/applications/RunningProcessesView.java
+++ b/src/com/android/settings/applications/RunningProcessesView.java
@@ -16,6 +16,7 @@
package com.android.settings.applications;
+import com.android.internal.util.MemInfoReader;
import com.android.settings.R;
import android.app.ActivityManager;
@@ -24,9 +25,7 @@ import android.app.Fragment;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
-import android.os.StrictMode;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.preference.PreferenceActivity;
import android.text.format.DateUtils;
import android.text.format.Formatter;
@@ -42,7 +41,6 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.AbsListView.RecyclerListener;
-import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -51,9 +49,6 @@ public class RunningProcessesView extends FrameLayout
implements AdapterView.OnItemClickListener, RecyclerListener,
RunningState.OnRefreshUiListener {
- // Memory pages are 4K.
- static final long PAGE_SIZE = 4*1024;
-
long SECONDARY_SERVER_MEM;
final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>();
@@ -85,9 +80,9 @@ public class RunningProcessesView extends FrameLayout
long mLastAvailMemory = -1;
Dialog mCurDialog;
-
- byte[] mBuffer = new byte[1024];
-
+
+ MemInfoReader mMemInfoReader = new MemInfoReader();
+
public static class ActiveItem {
View mRootView;
RunningState.BaseItem mItem;
@@ -304,71 +299,7 @@ public class RunningProcessesView extends FrameLayout
}
}
}
-
- private boolean matchText(byte[] buffer, int index, String text) {
- int N = text.length();
- if ((index+N) >= buffer.length) {
- return false;
- }
- for (int i=0; i<N; i++) {
- if (buffer[index+i] != text.charAt(i)) {
- return false;
- }
- }
- return true;
- }
-
- private long extractMemValue(byte[] buffer, int index) {
- while (index < buffer.length && buffer[index] != '\n') {
- if (buffer[index] >= '0' && buffer[index] <= '9') {
- int start = index;
- index++;
- while (index < buffer.length && buffer[index] >= '0'
- && buffer[index] <= '9') {
- index++;
- }
- String str = new String(buffer, 0, start, index-start);
- return ((long)Integer.parseInt(str)) * 1024;
- }
- index++;
- }
- return 0;
- }
-
- private long readAvailMem() {
- // Permit disk reads here, as /proc/meminfo isn't really "on
- // disk" and should be fast. TODO: make BlockGuard ignore
- // /proc/ and /sys/ files perhaps?
- StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
- try {
- long memFree = 0;
- long memCached = 0;
- FileInputStream is = new FileInputStream("/proc/meminfo");
- int len = is.read(mBuffer);
- is.close();
- final int BUFLEN = mBuffer.length;
- for (int i=0; i<len && (memFree == 0 || memCached == 0); i++) {
- if (matchText(mBuffer, i, "MemFree")) {
- i += 7;
- memFree = extractMemValue(mBuffer, i);
- } else if (matchText(mBuffer, i, "Cached")) {
- i += 6;
- memCached = extractMemValue(mBuffer, i);
- }
- while (i < BUFLEN && mBuffer[i] != '\n') {
- i++;
- }
- }
- return memFree + memCached;
- } catch (java.io.FileNotFoundException e) {
- } catch (java.io.IOException e) {
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
- return 0;
- }
-
void refreshUi(boolean dataChanged) {
if (dataChanged) {
ServiceListAdapter adapter = (ServiceListAdapter)(mListView.getAdapter());
@@ -383,11 +314,13 @@ public class RunningProcessesView extends FrameLayout
// This is the amount of available memory until we start killing
// background services.
- long availMem = readAvailMem() - SECONDARY_SERVER_MEM;
+ mMemInfoReader.readMemInfo();
+ long availMem = mMemInfoReader.getFreeSize() + mMemInfoReader.getCachedSize()
+ - SECONDARY_SERVER_MEM;
if (availMem < 0) {
availMem = 0;
}
-
+
synchronized (mState.mLock) {
if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses
|| mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory
@@ -395,10 +328,14 @@ public class RunningProcessesView extends FrameLayout
mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses;
mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory;
mLastAvailMemory = availMem;
- String sizeStr = Formatter.formatShortFileSize(getContext(),
- mLastAvailMemory + mLastBackgroundProcessMemory);
+ long freeMem = mLastAvailMemory + mLastBackgroundProcessMemory;
+ String sizeStr = Formatter.formatShortFileSize(getContext(), freeMem);
mBackgroundProcessText.setText(getResources().getString(
R.string.service_background_processes, sizeStr));
+ sizeStr = Formatter.formatShortFileSize(getContext(),
+ mMemInfoReader.getTotalSize() - freeMem);
+ mForegroundProcessText.setText(getResources().getString(
+ R.string.service_foreground_processes, sizeStr));
}
if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses
|| mLastForegroundProcessMemory != mState.mForegroundProcessMemory
@@ -408,15 +345,18 @@ public class RunningProcessesView extends FrameLayout
mLastForegroundProcessMemory = mState.mForegroundProcessMemory;
mLastNumServiceProcesses = mState.mNumServiceProcesses;
mLastServiceProcessMemory = mState.mServiceProcessMemory;
+ /*
String sizeStr = Formatter.formatShortFileSize(getContext(),
mLastForegroundProcessMemory + mLastServiceProcessMemory);
mForegroundProcessText.setText(getResources().getString(
R.string.service_foreground_processes, sizeStr));
+ */
}
- float totalMem = availMem + mLastBackgroundProcessMemory
- + mLastForegroundProcessMemory + mLastServiceProcessMemory;
- mColorBar.setRatios(mLastForegroundProcessMemory/totalMem,
+ float totalMem = mMemInfoReader.getTotalSize();
+ float totalShownMem = availMem + mLastBackgroundProcessMemory
+ + mLastServiceProcessMemory;
+ mColorBar.setRatios((totalMem-totalShownMem)/totalMem,
mLastServiceProcessMemory/totalMem,
mLastBackgroundProcessMemory/totalMem);
}
@@ -482,10 +422,10 @@ public class RunningProcessesView extends FrameLayout
mAdapter.setShowBackground(false);
}
});
-
- // Magic! Implementation detail! Don't count on this!
- SECONDARY_SERVER_MEM =
- Integer.valueOf(SystemProperties.get("ro.SECONDARY_SERVER_MEM"))*PAGE_SIZE;
+
+ ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
+ mAm.getMemoryInfo(memInfo);
+ SECONDARY_SERVER_MEM = memInfo.secondaryServerThreshold;
}
public void doPause() {
diff --git a/src/com/android/settings/applications/RunningState.java b/src/com/android/settings/applications/RunningState.java
index 8ca17a5..552aa56 100644
--- a/src/com/android/settings/applications/RunningState.java
+++ b/src/com/android/settings/applications/RunningState.java
@@ -28,7 +28,6 @@ import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
-import android.os.Debug;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -250,7 +249,9 @@ public class RunningState {
ActivityManager.RunningAppProcessInfo mRunningProcessInfo;
MergedItem mMergedItem;
-
+
+ boolean mInteresting;
+
// Purely for sorting.
boolean mIsSystem;
boolean mIsStarted;
@@ -388,8 +389,8 @@ public class RunningState {
return changed;
}
- boolean updateSize(Context context, Debug.MemoryInfo mem, int curSeq) {
- mSize = ((long)mem.getTotalPss()) * 1024;
+ boolean updateSize(Context context, long pss, int curSeq) {
+ mSize = pss * 1024;
if (mCurSeq == curSeq) {
String sizeStr = Formatter.formatShortFileSize(
context, mSize);
@@ -616,7 +617,8 @@ public class RunningState {
return true;
}
if ((pi.flags&ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT) == 0
- && pi.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+ && pi.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+ && pi.importance < ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE
&& pi.importanceReasonCode
== ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN) {
return true;
@@ -718,13 +720,16 @@ public class RunningState {
mInterestingProcesses.add(proc);
}
proc.mCurSeq = mSequence;
+ proc.mInteresting = true;
proc.ensureLabel(pm);
+ } else {
+ proc.mInteresting = false;
}
proc.mRunningSeq = mSequence;
proc.mRunningProcessInfo = pi;
}
-
+
// Build the chains from client processes to the process they are
// dependent on; also remove any old running processes.
int NRP = mRunningProcesses.size();
@@ -756,7 +761,7 @@ public class RunningState {
int NHP = mInterestingProcesses.size();
for (int i=0; i<NHP; i++) {
ProcessItem proc = mInterestingProcesses.get(i);
- if (mRunningProcesses.get(proc.mPid) == null) {
+ if (!proc.mInteresting || mRunningProcesses.get(proc.mPid) == null) {
changed = true;
mInterestingProcesses.remove(i);
i--;
@@ -964,12 +969,12 @@ public class RunningState {
for (int i=0; i<numProc; i++) {
pids[i] = mAllProcessItems.get(i).mPid;
}
- Debug.MemoryInfo[] mem = ActivityManagerNative.getDefault()
- .getProcessMemoryInfo(pids);
+ long[] pss = ActivityManagerNative.getDefault()
+ .getProcessPss(pids);
int bgIndex = 0;
for (int i=0; i<pids.length; i++) {
ProcessItem proc = mAllProcessItems.get(i);
- changed |= proc.updateSize(context, mem[i], mSequence);
+ changed |= proc.updateSize(context, pss[i], mSequence);
if (proc.mCurSeq == mSequence) {
serviceProcessMemory += proc.mSize;
} else if (proc.mRunningProcessInfo.importance >=
diff --git a/src/com/android/settings/bluetooth/A2dpProfile.java b/src/com/android/settings/bluetooth/A2dpProfile.java
index e8582f3..b7ba44d 100644
--- a/src/com/android/settings/bluetooth/A2dpProfile.java
+++ b/src/com/android/settings/bluetooth/A2dpProfile.java
@@ -138,14 +138,10 @@ final class A2dpProfile implements LocalBluetoothProfile {
return ORDINAL;
}
- public int getNameResource() {
+ public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_a2dp;
}
- public int getDisconnectResource(BluetoothDevice device) {
- return R.string.bluetooth_disconnect_a2dp_profile;
- }
-
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = mService.getConnectionState(device);
switch (state) {
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java
index 00e342c..e4f11a2 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java
@@ -42,6 +42,9 @@ final class BluetoothDeviceFilter {
/** Bonded devices only filter (referenced directly). */
static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
+ /** Unbonded devices only filter (referenced directly). */
+ static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter();
+
/** Table of singleton filter objects. */
private static final Filter[] FILTERS = {
ALL_FILTER, // FILTER_TYPE_ALL
@@ -85,6 +88,13 @@ final class BluetoothDeviceFilter {
}
}
+ /** Filter that matches only unbonded devices. */
+ private static final class UnbondedDeviceFilter implements Filter {
+ public boolean matches(BluetoothDevice device) {
+ return device.getBondState() != BluetoothDevice.BOND_BONDED;
+ }
+ }
+
/** Parent class of filters based on UUID and/or Bluetooth class. */
private abstract static class ClassUuidFilter implements Filter {
abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass);
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 391c941..c659f70 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.preference.Preference;
+import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
@@ -49,8 +50,6 @@ public final class BluetoothDevicePreference extends Preference implements
private final CachedBluetoothDevice mCachedDevice;
- private ImageView mDeviceSettings;
-
private OnClickListener mOnSettingsClickListener;
private AlertDialog mDisconnectDialog;
@@ -66,7 +65,9 @@ public final class BluetoothDevicePreference extends Preference implements
mCachedDevice = cachedDevice;
- setWidgetLayoutResource(R.layout.preference_bluetooth);
+ if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
+ setWidgetLayoutResource(R.layout.preference_bluetooth);
+ }
mCachedDevice.registerCallback(this);
@@ -99,7 +100,17 @@ public final class BluetoothDevicePreference extends Preference implements
*/
setTitle(mCachedDevice.getName());
- setSummary(getConnectionSummary());
+ int summaryResId = getConnectionSummary();
+ if (summaryResId != 0) {
+ setSummary(summaryResId);
+ } else {
+ setSummary(null); // empty summary for unpaired devices
+ }
+
+ int iconResId = getBtClassDrawable();
+ if (iconResId != 0) {
+ setIcon(iconResId);
+ }
// Used to gray out the item
setEnabled(!mCachedDevice.isBusy());
@@ -115,50 +126,26 @@ public final class BluetoothDevicePreference extends Preference implements
setDependency("bt_checkbox");
}
- super.onBindView(view);
-
- ImageView btClass = (ImageView) view.findViewById(android.R.id.icon);
- btClass.setImageResource(getBtClassDrawable());
- btClass.setAlpha(isEnabled() ? 255 : sDimAlpha);
- btClass.setVisibility(View.VISIBLE);
- mDeviceSettings = (ImageView) view.findViewById(R.id.deviceDetails);
- if (mOnSettingsClickListener != null) {
- mDeviceSettings.setOnClickListener(this);
- mDeviceSettings.setTag(mCachedDevice);
- mDeviceSettings.setAlpha(isEnabled() ? 255 : sDimAlpha);
- } else { // Hide the settings icon and divider
- mDeviceSettings.setVisibility(View.GONE);
- View divider = view.findViewById(R.id.divider);
- if (divider != null) {
- divider.setVisibility(View.GONE);
+ if (mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
+ ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails);
+ if (deviceDetails != null) {
+ deviceDetails.setOnClickListener(this);
+ deviceDetails.setTag(mCachedDevice);
+ deviceDetails.setAlpha(isEnabled() ? 255 : sDimAlpha);
}
}
- LayoutInflater inflater = (LayoutInflater)
- getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- ViewGroup profilesGroup = (ViewGroup) view.findViewById(R.id.profileIcons);
- for (LocalBluetoothProfile profile : mCachedDevice.getProfiles()) {
- int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass());
- if (iconResource != 0) {
- Drawable icon = getContext().getResources().getDrawable(iconResource);
- inflater.inflate(R.layout.profile_icon_small, profilesGroup, true);
- ImageView imageView =
- (ImageView) profilesGroup.getChildAt(profilesGroup.getChildCount() - 1);
- imageView.setImageDrawable(icon);
- boolean profileEnabled = mCachedDevice.isConnectedProfile(profile);
- imageView.setAlpha(profileEnabled ? 255 : sDimAlpha);
- }
- }
+ super.onBindView(view);
}
public void onClick(View v) {
- if (v == mDeviceSettings) {
- if (mOnSettingsClickListener != null) {
- mOnSettingsClickListener.onClick(v);
- }
+ // Should never be null by construction
+ if (mOnSettingsClickListener != null) {
+ mOnSettingsClickListener.onClick(v);
}
}
+ @Override
public boolean equals(Object o) {
if ((o == null) || !(o instanceof BluetoothDevicePreference)) {
return false;
@@ -167,6 +154,7 @@ public final class BluetoothDevicePreference extends Preference implements
((BluetoothDevicePreference) o).mCachedDevice);
}
+ @Override
public int hashCode() {
return mCachedDevice.hashCode();
}
@@ -174,8 +162,8 @@ public final class BluetoothDevicePreference extends Preference implements
@Override
public int compareTo(Preference another) {
if (!(another instanceof BluetoothDevicePreference)) {
- // Put other preference types above us
- return 1;
+ // Rely on default sort
+ return super.compareTo(another);
}
return mCachedDevice
@@ -201,7 +189,8 @@ public final class BluetoothDevicePreference extends Preference implements
if (TextUtils.isEmpty(name)) {
name = context.getString(R.string.bluetooth_device);
}
- String message = context.getString(R.string.bluetooth_disconnect_blank, name);
+ String message = context.getString(R.string.bluetooth_disconnect_all_profiles, name);
+ String title = context.getString(R.string.bluetooth_disconnect_title);
DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
@@ -210,7 +199,7 @@ public final class BluetoothDevicePreference extends Preference implements
};
mDisconnectDialog = Utils.showDisconnectDialog(context,
- mDisconnectDialog, disconnectListener, name, message);
+ mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
}
private void pair() {
@@ -222,24 +211,53 @@ public final class BluetoothDevicePreference extends Preference implements
private int getConnectionSummary() {
final CachedBluetoothDevice cachedDevice = mCachedDevice;
- final BluetoothDevice device = cachedDevice.getDevice();
- // if any profiles are connected or busy, return that status
+ boolean profileConnected = false; // at least one profile is connected
+ boolean a2dpNotConnected = false; // A2DP is preferred but not connected
+ boolean headsetNotConnected = false; // Headset is preferred but not connected
+
for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
int connectionStatus = cachedDevice.getProfileConnectionState(profile);
- if (connectionStatus != BluetoothProfile.STATE_DISCONNECTED) {
- return Utils.getConnectionStateSummary(connectionStatus);
+ switch (connectionStatus) {
+ case BluetoothProfile.STATE_CONNECTING:
+ case BluetoothProfile.STATE_DISCONNECTING:
+ return Utils.getConnectionStateSummary(connectionStatus);
+
+ case BluetoothProfile.STATE_CONNECTED:
+ profileConnected = true;
+ break;
+
+ case BluetoothProfile.STATE_DISCONNECTED:
+ if (profile.isProfileReady() && profile.isPreferred(cachedDevice.getDevice())) {
+ if (profile instanceof A2dpProfile) {
+ a2dpNotConnected = true;
+ } else if (profile instanceof HeadsetProfile) {
+ headsetNotConnected = true;
+ }
+ }
+ break;
+ }
+ }
+
+ if (profileConnected) {
+ if (a2dpNotConnected && headsetNotConnected) {
+ return R.string.bluetooth_connected_no_headset_no_a2dp;
+ } else if (a2dpNotConnected) {
+ return R.string.bluetooth_connected_no_a2dp;
+ } else if (headsetNotConnected) {
+ return R.string.bluetooth_connected_no_headset;
+ } else {
+ return R.string.bluetooth_connected;
}
}
switch (cachedDevice.getBondState()) {
- case BluetoothDevice.BOND_BONDED:
- return R.string.bluetooth_paired;
case BluetoothDevice.BOND_BONDING:
return R.string.bluetooth_pairing;
+
+ case BluetoothDevice.BOND_BONDED:
case BluetoothDevice.BOND_NONE:
- return R.string.bluetooth_not_connected;
default:
return 0;
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
index 40bf5bc..babf1e2 100644..100755
--- a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
+++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
@@ -21,11 +21,11 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.Handler;
import android.os.SystemProperties;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
import android.preference.Preference;
+import android.text.format.DateUtils;
import com.android.settings.R;
@@ -34,7 +34,7 @@ import com.android.settings.R;
* checkbox. It sets/unsets discoverability and keeps track of how much time
* until the the discoverability is automatically turned off.
*/
-final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener {
+final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceClickListener {
private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT =
"debug.bt.discoverable_time";
@@ -44,6 +44,10 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
private static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600;
static final int DISCOVERABLE_TIMEOUT_NEVER = 0;
+ // Bluetooth advanced settings screen was replaced with action bar items.
+ // Use the same preference key for discoverable timeout as the old ListPreference.
+ private static final String KEY_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout";
+
private static final String VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES = "twomin";
private static final String VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES = "fivemin";
private static final String VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR = "onehour";
@@ -53,11 +57,17 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
private final Context mContext;
private final Handler mUiHandler;
- private final CheckBoxPreference mCheckBoxPreference;
- private final ListPreference mTimeoutListPreference;
+ private final Preference mDiscoveryPreference;
private final LocalBluetoothAdapter mLocalAdapter;
+ private final SharedPreferences mSharedPreferences;
+
+ private boolean mDiscoverable;
+ private int mNumberOfPairedDevices;
+
+ private int mTimeoutSecs = -1;
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -78,21 +88,13 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
};
BluetoothDiscoverableEnabler(Context context, LocalBluetoothAdapter adapter,
- CheckBoxPreference checkBoxPreference, ListPreference timeoutListPreference) {
+ Preference discoveryPreference) {
mContext = context;
mUiHandler = new Handler();
- mCheckBoxPreference = checkBoxPreference;
- mTimeoutListPreference = timeoutListPreference;
-
- checkBoxPreference.setPersistent(false);
- // we actually want to persist this since can't infer from BT device state
- mTimeoutListPreference.setPersistent(true);
-
mLocalAdapter = adapter;
- if (adapter == null) {
- // Bluetooth not supported
- checkBoxPreference.setEnabled(false);
- }
+ mDiscoveryPreference = discoveryPreference;
+ mSharedPreferences = discoveryPreference.getSharedPreferences();
+ discoveryPreference.setPersistent(false);
}
public void resume() {
@@ -102,8 +104,7 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
mContext.registerReceiver(mReceiver, filter);
- mCheckBoxPreference.setOnPreferenceChangeListener(this);
- mTimeoutListPreference.setOnPreferenceChangeListener(this);
+ mDiscoveryPreference.setOnPreferenceClickListener(this);
handleModeChanged(mLocalAdapter.getScanMode());
}
@@ -113,20 +114,14 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
}
mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
- mCheckBoxPreference.setOnPreferenceChangeListener(null);
- mTimeoutListPreference.setOnPreferenceChangeListener(null);
mContext.unregisterReceiver(mReceiver);
+ mDiscoveryPreference.setOnPreferenceClickListener(null);
}
- public boolean onPreferenceChange(Preference preference, Object value) {
- if (preference == mCheckBoxPreference) {
- // Turn on/off BT discoverability
- setEnabled((Boolean) value);
- } else if (preference == mTimeoutListPreference) {
- mTimeoutListPreference.setValue((String) value);
- setEnabled(true);
- }
-
+ public boolean onPreferenceClick(Preference preference) {
+ // toggle discoverability
+ mDiscoverable = !mDiscoverable;
+ setEnabled(mDiscoverable);
return true;
}
@@ -138,9 +133,8 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
long endTimestamp = System.currentTimeMillis() + timeout * 1000L;
LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp);
- updateCountdownSummary();
-
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
+ updateCountdownSummary();
} else {
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
}
@@ -148,22 +142,63 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
private void updateTimerDisplay(int timeout) {
if (getDiscoverableTimeout() == DISCOVERABLE_TIMEOUT_NEVER) {
- mCheckBoxPreference.setSummaryOn(
- mContext.getString(R.string.bluetooth_is_discoverable_always));
+ mDiscoveryPreference.setSummary(R.string.bluetooth_is_discoverable_always);
} else {
- mCheckBoxPreference.setSummaryOn(
- mContext.getString(R.string.bluetooth_is_discoverable, timeout));
+ String textTimeout = formatTimeRemaining(timeout);
+ mDiscoveryPreference.setSummary(mContext.getString(R.string.bluetooth_is_discoverable,
+ textTimeout));
+ }
+ }
+
+ private static String formatTimeRemaining(int timeout) {
+ StringBuilder sb = new StringBuilder(6); // "mmm:ss"
+ int min = timeout / 60;
+ sb.append(min).append(':');
+ int sec = timeout - (min * 60);
+ if (sec < 10) {
+ sb.append('0');
+ }
+ sb.append(sec);
+ return sb.toString();
+ }
+
+ void setDiscoverableTimeout(int index) {
+ String timeoutValue;
+ switch (index) {
+ case 0:
+ default:
+ mTimeoutSecs = DISCOVERABLE_TIMEOUT_TWO_MINUTES;
+ timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES;
+ break;
+
+ case 1:
+ mTimeoutSecs = DISCOVERABLE_TIMEOUT_FIVE_MINUTES;
+ timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES;
+ break;
+
+ case 2:
+ mTimeoutSecs = DISCOVERABLE_TIMEOUT_ONE_HOUR;
+ timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR;
+ break;
+
+ case 3:
+ mTimeoutSecs = DISCOVERABLE_TIMEOUT_NEVER;
+ timeoutValue = VALUE_DISCOVERABLE_TIMEOUT_NEVER;
+ break;
}
+ mSharedPreferences.edit().putString(KEY_DISCOVERABLE_TIMEOUT, timeoutValue).apply();
+ setEnabled(true); // enable discovery and reset timer
}
private int getDiscoverableTimeout() {
+ if (mTimeoutSecs != -1) {
+ return mTimeoutSecs;
+ }
+
int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1);
if (timeout < 0) {
- String timeoutValue = mTimeoutListPreference.getValue();
- if (timeoutValue == null) {
- mTimeoutListPreference.setValue(VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES);
- return DISCOVERABLE_TIMEOUT_TWO_MINUTES;
- }
+ String timeoutValue = mSharedPreferences.getString(KEY_DISCOVERABLE_TIMEOUT,
+ VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES);
if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_NEVER)) {
timeout = DISCOVERABLE_TIMEOUT_NEVER;
@@ -175,16 +210,48 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
timeout = DISCOVERABLE_TIMEOUT_TWO_MINUTES;
}
}
-
+ mTimeoutSecs = timeout;
return timeout;
}
- private void handleModeChanged(int mode) {
+ int getDiscoverableTimeoutIndex() {
+ int timeout = getDiscoverableTimeout();
+ switch (timeout) {
+ case DISCOVERABLE_TIMEOUT_TWO_MINUTES:
+ default:
+ return 0;
+
+ case DISCOVERABLE_TIMEOUT_FIVE_MINUTES:
+ return 1;
+
+ case DISCOVERABLE_TIMEOUT_ONE_HOUR:
+ return 2;
+
+ case DISCOVERABLE_TIMEOUT_NEVER:
+ return 3;
+ }
+ }
+
+ void setNumberOfPairedDevices(int pairedDevices) {
+ mNumberOfPairedDevices = pairedDevices;
+ handleModeChanged(mLocalAdapter.getScanMode());
+ }
+
+ void handleModeChanged(int mode) {
if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
- mCheckBoxPreference.setChecked(true);
+ mDiscoverable = true;
updateCountdownSummary();
} else {
- mCheckBoxPreference.setChecked(false);
+ mDiscoverable = false;
+ setSummaryNotDiscoverable();
+ }
+ }
+
+ private void setSummaryNotDiscoverable() {
+ if (mNumberOfPairedDevices != 0) {
+ mDiscoveryPreference.setSummary(R.string.bluetooth_only_visible_to_paired_devices);
+ } else {
+ mDiscoveryPreference.setSummary(R.string.bluetooth_not_visible_to_other_devices);
}
}
@@ -199,7 +266,7 @@ final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChang
if (currentTimestamp > endTimestamp) {
// We're still in discoverable mode, but maybe there isn't a timeout.
- mCheckBoxPreference.setSummaryOn(null);
+ updateTimerDisplay(0);
return;
}
diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java
index 79f23bb..f08e083 100644
--- a/src/com/android/settings/bluetooth/BluetoothEnabler.java
+++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java
@@ -16,28 +16,27 @@
package com.android.settings.bluetooth;
-import com.android.settings.R;
-import com.android.settings.WirelessSettings;
-
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.preference.Preference;
-import android.preference.CheckBoxPreference;
import android.provider.Settings;
+import android.widget.CompoundButton;
+import android.widget.Switch;
import android.widget.Toast;
+import com.android.settings.R;
+import com.android.settings.WirelessSettings;
+
/**
* BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox
* preference. It turns on/off Bluetooth and ensures the summary of the
* preference reflects the current state.
*/
-public final class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
+public final class BluetoothEnabler implements CompoundButton.OnCheckedChangeListener {
private final Context mContext;
- private final CheckBoxPreference mCheckBox;
- private final CharSequence mOriginalSummary;
+ private Switch mSwitch;
private final LocalBluetoothAdapter mLocalAdapter;
private final IntentFilter mIntentFilter;
@@ -50,17 +49,15 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList
}
};
- public BluetoothEnabler(Context context, CheckBoxPreference checkBox) {
+ public BluetoothEnabler(Context context, Switch switch_) {
mContext = context;
- mCheckBox = checkBox;
- mOriginalSummary = checkBox.getSummary();
- checkBox.setPersistent(false);
+ mSwitch = switch_;
LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
if (manager == null) {
// Bluetooth is not supported
mLocalAdapter = null;
- checkBox.setEnabled(false);
+ mSwitch.setEnabled(false);
} else {
mLocalAdapter = manager.getBluetoothAdapter();
}
@@ -69,6 +66,7 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList
public void resume() {
if (mLocalAdapter == null) {
+ mSwitch.setEnabled(false);
return;
}
@@ -76,7 +74,7 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList
handleStateChanged(mLocalAdapter.getBluetoothState());
mContext.registerReceiver(mReceiver, mIntentFilter);
- mCheckBox.setOnPreferenceChangeListener(this);
+ mSwitch.setOnCheckedChangeListener(this);
}
public void pause() {
@@ -85,51 +83,57 @@ public final class BluetoothEnabler implements Preference.OnPreferenceChangeList
}
mContext.unregisterReceiver(mReceiver);
- mCheckBox.setOnPreferenceChangeListener(null);
+ mSwitch.setOnCheckedChangeListener(null);
}
- public boolean onPreferenceChange(Preference preference, Object value) {
- boolean enable = (Boolean) value;
+ public void setSwitch(Switch switch_) {
+ if (mSwitch == switch_) return;
+ mSwitch.setOnCheckedChangeListener(null);
+ mSwitch = switch_;
+ mSwitch.setOnCheckedChangeListener(this);
+
+ int bluetoothState = BluetoothAdapter.STATE_OFF;
+ if (mLocalAdapter != null) bluetoothState = mLocalAdapter.getBluetoothState();
+ boolean isOn = bluetoothState == BluetoothAdapter.STATE_ON;
+ boolean isOff = bluetoothState == BluetoothAdapter.STATE_OFF;
+ mSwitch.setChecked(isOn);
+ mSwitch.setEnabled(isOn || isOff);
+ }
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Show toast message if Bluetooth is not allowed in airplane mode
- if (enable && !WirelessSettings
- .isRadioAllowed(mContext, Settings.System.RADIO_BLUETOOTH)) {
- Toast.makeText(mContext, R.string.wifi_in_airplane_mode,
- Toast.LENGTH_SHORT).show();
- return false;
+ if (isChecked &&
+ !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_BLUETOOTH)) {
+ Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
+ // Reset switch to off
+ buttonView.setChecked(false);
}
- mLocalAdapter.setBluetoothEnabled(enable);
- mCheckBox.setEnabled(false);
-
- // Don't update UI to opposite state until we're sure
- return false;
+ if (mLocalAdapter != null) {
+ mLocalAdapter.setBluetoothEnabled(isChecked);
+ }
+ mSwitch.setEnabled(false);
}
void handleStateChanged(int state) {
switch (state) {
case BluetoothAdapter.STATE_TURNING_ON:
- mCheckBox.setSummary(R.string.wifi_starting);
- mCheckBox.setEnabled(false);
+ mSwitch.setEnabled(false);
break;
case BluetoothAdapter.STATE_ON:
- mCheckBox.setChecked(true);
- mCheckBox.setSummary(null);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(true);
+ mSwitch.setEnabled(true);
break;
case BluetoothAdapter.STATE_TURNING_OFF:
- mCheckBox.setSummary(R.string.wifi_stopping);
- mCheckBox.setEnabled(false);
+ mSwitch.setEnabled(false);
break;
case BluetoothAdapter.STATE_OFF:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(mOriginalSummary);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(true);
break;
default:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(R.string.wifi_error);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(true);
}
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothEventManager.java b/src/com/android/settings/bluetooth/BluetoothEventManager.java
index 6f83766..9140b25 100644
--- a/src/com/android/settings/bluetooth/BluetoothEventManager.java
+++ b/src/com/android/settings/bluetooth/BluetoothEventManager.java
@@ -225,7 +225,7 @@ final class BluetoothEventManager {
Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
return;
}
- if (mDeviceManager.onDeviceDisappeared(cachedDevice)) {
+ if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
synchronized (mCallbacks) {
for (BluetoothCallback callback : mCallbacks) {
callback.onDeviceDeleted(cachedDevice);
@@ -283,7 +283,7 @@ final class BluetoothEventManager {
// if the device is undocked, remove it from the list as well
if (!device.getAddress().equals(getDockedDeviceAddress(context))) {
- mDeviceManager.onDeviceDisappeared(cachedDevice);
+ cachedDevice.setVisible(false);
}
}
int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
@@ -361,7 +361,7 @@ final class BluetoothEventManager {
if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
if (cachedDevice != null) {
- mDeviceManager.onDeviceDisappeared(cachedDevice);
+ cachedDevice.setVisible(false);
}
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothFindNearby.java b/src/com/android/settings/bluetooth/BluetoothFindNearby.java
deleted file mode 100644
index 066f4f6..0000000
--- a/src/com/android/settings/bluetooth/BluetoothFindNearby.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.bluetooth;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-
-import com.android.settings.R;
-
-/**
- * Fragment to scan and show the discoverable devices.
- */
-public final class BluetoothFindNearby extends DeviceListPreferenceFragment {
-
- @Override
- void addPreferencesForActivity() {
- addPreferencesFromResource(R.xml.device_picker);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mSelectedDevice != null) {
- CachedBluetoothDeviceManager manager = mLocalManager.getCachedDeviceManager();
- CachedBluetoothDevice device = manager.findDevice(mSelectedDevice);
- if (device != null && device.getBondState() == BluetoothDevice.BOND_BONDED) {
- // selected device was paired, so return from this screen
- finish();
- return;
- }
- }
- mLocalAdapter.startScanning(true);
- }
-
- @Override
- void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
- mLocalAdapter.stopScanning();
- super.onDevicePreferenceClick(btPreference);
- }
-
- public void onDeviceBondStateChanged(CachedBluetoothDevice
- cachedDevice, int bondState) {
- if (bondState == BluetoothDevice.BOND_BONDED) {
- // return from scan screen after successful auto-pairing
- finish();
- }
- }
-
- @Override
- public void onBluetoothStateChanged(int bluetoothState) {
- super.onBluetoothStateChanged(bluetoothState);
-
- if (bluetoothState == BluetoothAdapter.STATE_ON) {
- mLocalAdapter.startScanning(false);
- }
- }
-}
diff --git a/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
new file mode 100644
index 0000000..c00aff3
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothNameDialogFragment.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.android.settings.R;
+
+/**
+ * Dialog fragment for renaming the local Bluetooth device.
+ */
+final class BluetoothNameDialogFragment extends DialogFragment implements TextWatcher {
+ private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248;
+
+ private AlertDialog mAlertDialog;
+ private Button mOkButton;
+
+ // accessed from inner class (not private to avoid thunks)
+ static final String TAG = "BluetoothNameDialogFragment";
+ final LocalBluetoothAdapter mLocalAdapter;
+ EditText mDeviceNameView;
+
+ // This flag is set when the name is updated by code, to distinguish from user changes
+ private boolean mDeviceNameUpdated;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
+ updateDeviceName();
+ } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) &&
+ (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) ==
+ BluetoothAdapter.STATE_ON)) {
+ updateDeviceName();
+ }
+ }
+ };
+
+ public BluetoothNameDialogFragment(LocalBluetoothAdapter adapter) {
+ mLocalAdapter = adapter;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mAlertDialog = new AlertDialog.Builder(getActivity())
+ .setIcon(android.R.drawable.ic_dialog_info)
+ .setTitle(R.string.bluetooth_rename_device)
+ .setView(createDialogView())
+ .setPositiveButton(R.string.bluetooth_rename_button,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (mLocalAdapter != null) {
+ String deviceName = mDeviceNameView.getText().toString();
+ Log.d(TAG, "Setting device name to " + deviceName);
+ mLocalAdapter.setName(deviceName);
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+
+ return mAlertDialog;
+ }
+
+ private View createDialogView() {
+ final LayoutInflater layoutInflater = (LayoutInflater)getActivity()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View view = layoutInflater.inflate(R.layout.dialog_edittext, null);
+ mDeviceNameView = (EditText) view.findViewById(R.id.edittext);
+ mDeviceNameView.setFilters(new InputFilter[] {
+ new Utf8ByteLengthFilter(BLUETOOTH_NAME_MAX_LENGTH_BYTES)
+ });
+ mDeviceNameView.addTextChangedListener(this);
+ return view;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mAlertDialog = null;
+ mDeviceNameView = null;
+ mOkButton = null;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mOkButton == null) {
+ mOkButton = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ mOkButton.setEnabled(false); // Ok button is enabled when the user edits the name
+ }
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+ getActivity().registerReceiver(mReceiver, filter);
+ updateDeviceName();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getActivity().unregisterReceiver(mReceiver);
+ }
+
+ void updateDeviceName() {
+ if (mLocalAdapter != null && mLocalAdapter.isEnabled()) {
+ mDeviceNameUpdated = true;
+ mDeviceNameView.setText(mLocalAdapter.getName());
+ }
+ }
+
+ public void afterTextChanged(Editable s) {
+ if (mDeviceNameUpdated) {
+ // Device name changed by code; disable Ok button until edited by user
+ mDeviceNameUpdated = false;
+ mOkButton.setEnabled(false);
+ } else {
+ mOkButton.setEnabled(s.length() != 0);
+ }
+ }
+
+ /* Not used */
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ /* Not used */
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
deleted file mode 100644
index f41689e..0000000
--- a/src/com/android/settings/bluetooth/BluetoothNamePreference.java
+++ /dev/null
@@ -1,143 +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.bluetooth;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.preference.EditTextPreference;
-import android.text.Editable;
-import android.text.InputFilter;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.widget.Button;
-import android.widget.EditText;
-
-/**
- * BluetoothNamePreference is the preference type for editing the device's
- * Bluetooth name. It asks the user for a name, and persists it via the
- * Bluetooth API.
- */
-public final class BluetoothNamePreference extends EditTextPreference implements TextWatcher {
-// private static final String TAG = "BluetoothNamePreference";
- private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248;
-
- private final LocalBluetoothAdapter mLocalAdapter;
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
- setSummaryToName();
- } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) &&
- (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) ==
- BluetoothAdapter.STATE_ON)) {
- setSummaryToName();
- }
- }
- };
-
- public BluetoothNamePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mLocalAdapter = LocalBluetoothManager.getInstance(context).getBluetoothAdapter();
-
- setSummaryToName();
- }
-
- public void resume() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
- getContext().registerReceiver(mReceiver, filter);
-
- // Make sure the OK button is disabled (if necessary) after rotation
- EditText et = getEditText();
- if (et != null) {
- et.setFilters(new InputFilter[] {
- new Utf8ByteLengthFilter(BLUETOOTH_NAME_MAX_LENGTH_BYTES)
- });
-
- et.addTextChangedListener(this);
- Dialog d = getDialog();
- if (d instanceof AlertDialog) {
- Button b = ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE);
- b.setEnabled(et.getText().length() > 0);
- }
- }
- }
-
- public void pause() {
- EditText et = getEditText();
- if (et != null) {
- et.removeTextChangedListener(this);
- }
- getContext().unregisterReceiver(mReceiver);
- }
-
- private void setSummaryToName() {
- if (mLocalAdapter != null && mLocalAdapter.isEnabled()) {
- setSummary(mLocalAdapter.getName());
- }
- }
-
- @Override
- protected boolean persistString(String value) {
- // Persist with Bluez instead of shared preferences
- if (mLocalAdapter != null) {
- mLocalAdapter.setName(value);
- }
- return true;
- }
-
- @Override
- protected void onClick() {
- super.onClick();
-
- // The dialog should be created by now
- EditText et = getEditText();
- if (et != null && mLocalAdapter != null) {
- et.setText(mLocalAdapter.getName());
- }
- }
-
- // TextWatcher interface
- public void afterTextChanged(Editable s) {
- Dialog d = getDialog();
- if (d instanceof AlertDialog) {
- ((AlertDialog) d).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(s.length() > 0);
- }
- }
-
- // TextWatcher interface
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // not used
- }
-
- // TextWatcher interface
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // not used
- }
-
-}
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
index 1b443c4..940d8d0 100644..100755
--- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java
@@ -24,26 +24,31 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.text.Editable;
+import android.text.Html;
import android.text.InputFilter;
import android.text.InputType;
+import android.text.Spanned;
import android.text.TextWatcher;
import android.text.InputFilter.LengthFilter;
import android.util.Log;
import android.view.View;
import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.settings.R;
+import android.view.KeyEvent;
/**
* BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation
* for pairing with a remote Bluetooth device. It is an activity that appears as a dialog.
*/
-public final class BluetoothPairingDialog extends AlertActivity implements DialogInterface.OnClickListener,
- TextWatcher {
+public final class BluetoothPairingDialog extends AlertActivity implements
+ CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener, TextWatcher {
private static final String TAG = "BluetoothPairingDialog";
private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
@@ -156,7 +161,7 @@ public final class BluetoothPairingDialog extends AlertActivity implements Dialo
final AlertController.AlertParams p = mAlertParams;
p.mIconId = android.R.drawable.ic_dialog_info;
p.mTitle = getString(R.string.bluetooth_pairing_request);
- p.mView = createView(deviceManager);
+ p.mView = createPinEntryView(deviceManager.getName(mDevice));
p.mPositiveButtonText = getString(android.R.string.ok);
p.mPositiveButtonListener = this;
p.mNegativeButtonText = getString(android.R.string.cancel);
@@ -167,56 +172,78 @@ public final class BluetoothPairingDialog extends AlertActivity implements Dialo
mOkButton.setEnabled(false);
}
- private View createView(CachedBluetoothDeviceManager deviceManager) {
+ private View createPinEntryView(String deviceName) {
View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
- String name = deviceManager.getName(mDevice);
TextView messageView = (TextView) view.findViewById(R.id.message);
+ TextView messageView2 = (TextView) view.findViewById(R.id.message_below_pin);
+ CheckBox alphanumericPin = (CheckBox) view.findViewById(R.id.alphanumeric_pin);
mPairingView = (EditText) view.findViewById(R.id.text);
mPairingView.addTextChangedListener(this);
+ alphanumericPin.setOnCheckedChangeListener(this);
+ int messageId1;
+ int messageId2;
+ int maxLength;
switch (mType) {
case BluetoothDevice.PAIRING_VARIANT_PIN:
- messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name));
- // Maximum of 16 characters in a PIN adb sync
- mPairingView.setFilters(new InputFilter[] {
- new LengthFilter(BLUETOOTH_PIN_MAX_LENGTH) });
+ messageId1 = R.string.bluetooth_enter_pin_msg;
+ messageId2 = R.string.bluetooth_enter_pin_other_device;
+ // Maximum of 16 characters in a PIN
+ maxLength = BLUETOOTH_PIN_MAX_LENGTH;
break;
case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
- messageView.setText(getString(R.string.bluetooth_enter_passkey_msg, name));
+ messageId1 = R.string.bluetooth_enter_passkey_msg;
+ messageId2 = R.string.bluetooth_enter_passkey_other_device;
// Maximum of 6 digits for passkey
- mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER |
- InputType.TYPE_NUMBER_FLAG_SIGNED);
- mPairingView.setFilters(new InputFilter[] {
- new LengthFilter(BLUETOOTH_PASSKEY_MAX_LENGTH)});
+ maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
+ alphanumericPin.setVisibility(View.GONE);
break;
+ default:
+ Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
+ return null;
+ }
+
+ // Format the message string, then parse HTML style tags
+ String messageText = getString(messageId1, deviceName);
+ messageView.setText(Html.fromHtml(messageText));
+ messageView2.setText(messageId2);
+ mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
+ mPairingView.setFilters(new InputFilter[] {
+ new LengthFilter(maxLength) });
+
+ return view;
+ }
+
+ private View createView(CachedBluetoothDeviceManager deviceManager) {
+ View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_confirm, null);
+ String name = deviceManager.getName(mDevice);
+ TextView messageView = (TextView) view.findViewById(R.id.message);
+
+ String messageText; // formatted string containing HTML style tags
+ switch (mType) {
case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
- mPairingView.setVisibility(View.GONE);
- messageView.setText(getString(R.string.bluetooth_confirm_passkey_msg, name,
- mPairingKey));
+ messageText = getString(R.string.bluetooth_confirm_passkey_msg,
+ name, mPairingKey);
break;
case BluetoothDevice.PAIRING_VARIANT_CONSENT:
- mPairingView.setVisibility(View.GONE);
- messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name));
+ case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
+ messageText = getString(R.string.bluetooth_incoming_pairing_msg, name);
break;
case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
- mPairingView.setVisibility(View.GONE);
- messageView.setText(getString(R.string.bluetooth_display_passkey_pin_msg, name,
- mPairingKey));
- break;
-
- case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
- mPairingView.setVisibility(View.GONE);
- messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name));
+ messageText = getString(R.string.bluetooth_display_passkey_pin_msg, name,
+ mPairingKey);
break;
default:
Log.e(TAG, "Incorrect pairing type received, not creating view");
+ return null;
}
+ messageView.setText(Html.fromHtml(messageText));
return view;
}
@@ -271,8 +298,8 @@ public final class BluetoothPairingDialog extends AlertActivity implements Dialo
}
public void afterTextChanged(Editable s) {
- if (s.length() > 0) {
- mOkButton.setEnabled(true);
+ if (mOkButton != null) {
+ mOkButton.setEnabled(s.length() > 0);
}
}
@@ -314,10 +341,21 @@ public final class BluetoothPairingDialog extends AlertActivity implements Dialo
mDevice.cancelPairingUserInput();
}
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ onCancel();
+ }
+ return super.onKeyDown(keyCode,event);
+ }
+
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case BUTTON_POSITIVE:
- onPair(mPairingView.getText().toString());
+ if (mPairingView != null) {
+ onPair(mPairingView.getText().toString());
+ } else {
+ onPair(null);
+ }
break;
case BUTTON_NEGATIVE:
@@ -335,4 +373,12 @@ public final class BluetoothPairingDialog extends AlertActivity implements Dialo
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ // change input type for soft keyboard to numeric or alphanumeric
+ if (isChecked) {
+ mPairingView.setInputType(InputType.TYPE_CLASS_TEXT);
+ } else {
+ mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER);
+ }
+ }
}
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
index de96d71..838e7b1 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
@@ -82,7 +82,7 @@ public final class BluetoothPairingRequest extends BroadcastReceiver {
String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
if (TextUtils.isEmpty(name)) {
- name = device != null ? device.getName() :
+ name = device != null ? device.getAliasName() :
context.getString(android.R.string.unknownName);
}
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
new file mode 100644
index 0000000..4d96140
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Button;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+import com.android.settings.R;
+
+/**
+ * BluetoothPermissionActivity shows a dialog for accepting incoming
+ * profile connection request from untrusted devices.
+ * It is also used to show a dialogue for accepting incoming phonebook
+ * read request. The request could be initiated by PBAP PCE or by HF AT+CPBR.
+ */
+public class BluetoothPermissionActivity extends AlertActivity implements
+ DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BluetoothPermissionActivity";
+ private static final boolean DEBUG = Utils.D;
+
+ private View mView;
+ private TextView messageView;
+ private Button mOkButton;
+ private BluetoothDevice mDevice;
+
+ private CheckBox mAlwaysAllowed;
+ private boolean mAlwaysAllowedValue = true;
+
+ private String mReturnPackage = null;
+ private String mReturnClass = null;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (mDevice.equals(device)) dismissDialog();
+ }
+ }
+ };
+
+ private void dismissDialog() {
+ this.dismiss();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent i = getIntent();
+ String action = i.getAction();
+ mDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ mReturnPackage = i.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
+ mReturnClass = i.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
+
+ if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
+ mDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (i.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+ BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) ==
+ BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION) {
+ showConnectionDialog();
+ } else {
+ showPbapDialog();
+ }
+ } else {
+ Log.e(TAG, "Error: this activity may be started only with intent "
+ + "ACTION_CONNECTION_ACCESS_REQUEST");
+ finish();
+ }
+ registerReceiver(mReceiver,
+ new IntentFilter(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL));
+ }
+
+ private void showConnectionDialog() {
+ final AlertController.AlertParams p = mAlertParams;
+ p.mIconId = android.R.drawable.ic_dialog_info;
+ p.mTitle = getString(R.string.bluetooth_connection_permission_request);
+ p.mView = createConnectionDialogView();
+ p.mPositiveButtonText = getString(R.string.yes);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(R.string.no);
+ p.mNegativeButtonListener = this;
+ mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+ setupAlert();
+ }
+
+ private void showPbapDialog() {
+ final AlertController.AlertParams p = mAlertParams;
+ p.mIconId = android.R.drawable.ic_dialog_info;
+ p.mTitle = getString(R.string.bluetooth_phonebook_request);
+ p.mView = createPbapDialogView();
+ p.mPositiveButtonText = getString(android.R.string.yes);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(android.R.string.no);
+ p.mNegativeButtonListener = this;
+ mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+ setupAlert();
+ }
+
+ private String createConnectionDisplayText() {
+ String mRemoteName = mDevice != null ? mDevice.getAliasName() : null;
+
+ if (mRemoteName == null) mRemoteName = getString(R.string.unknown);
+ String mMessage1 = getString(R.string.bluetooth_connection_dialog_text,
+ mRemoteName);
+ return mMessage1;
+ }
+
+ private String createPbapDisplayText() {
+ String mRemoteName = mDevice != null ? mDevice.getAliasName() : null;
+
+ if (mRemoteName == null) mRemoteName = getString(R.string.unknown);
+ String mMessage1 = getString(R.string.bluetooth_pb_acceptance_dialog_text,
+ mRemoteName, mRemoteName);
+ return mMessage1;
+ }
+
+ private View createConnectionDialogView() {
+ mView = getLayoutInflater().inflate(R.layout.bluetooth_connection_access, null);
+ messageView = (TextView)mView.findViewById(R.id.message);
+ messageView.setText(createConnectionDisplayText());
+ return mView;
+ }
+
+ private View createPbapDialogView() {
+ mView = getLayoutInflater().inflate(R.layout.bluetooth_pb_access, null);
+ messageView = (TextView)mView.findViewById(R.id.message);
+ messageView.setText(createPbapDisplayText());
+ mAlwaysAllowed = (CheckBox)mView.findViewById(R.id.bluetooth_pb_alwaysallowed);
+ mAlwaysAllowed.setChecked(true);
+ mAlwaysAllowed.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ mAlwaysAllowedValue = true;
+ } else {
+ mAlwaysAllowedValue = false;
+ }
+ }
+ });
+ return mView;
+ }
+
+ private void onPositive() {
+ if (DEBUG) Log.d(TAG, "onPositive mAlwaysAllowedValue: " + mAlwaysAllowedValue);
+ sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, true,
+ BluetoothDevice.EXTRA_ALWAYS_ALLOWED, mAlwaysAllowedValue);
+ finish();
+ }
+
+ private void onNegative() {
+ if (DEBUG) Log.d(TAG, "onNegative mAlwaysAllowedValue: " + mAlwaysAllowedValue);
+ sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, false,
+ null, false // dummy value, no effect since last param is null
+ );
+ finish();
+ }
+
+ private void sendIntentToReceiver(final String intentName, final boolean allowed,
+ final String extraName, final boolean extraValue) {
+ Intent intent = new Intent(intentName);
+
+ if (mReturnPackage != null && mReturnClass != null) {
+ intent.setClassName(mReturnPackage, mReturnClass);
+ }
+
+ intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+ allowed ? BluetoothDevice.CONNECTION_ACCESS_YES :
+ BluetoothDevice.CONNECTION_ACCESS_NO);
+
+ if (extraName != null) {
+ intent.putExtra(extraName, extraValue);
+ }
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN);
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ onPositive();
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ onNegative();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mReceiver);
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ return true;
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
new file mode 100644
index 0000000..51055af
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.PowerManager;
+import android.util.Log;
+
+import com.android.settings.R;
+
+/**
+ * BluetoothPermissionRequest is a receiver to receive Bluetooth connection
+ * access request.
+ */
+public final class BluetoothPermissionRequest extends BroadcastReceiver {
+
+ private static final String TAG = "BluetoothPermissionRequest";
+ private static final boolean DEBUG = Utils.V;
+ public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (DEBUG) Log.d(TAG, "onReceive");
+
+ if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
+ // convert broadcast intent into activity intent (same action string)
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+ BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
+ String returnPackage = intent.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
+ String returnClass = intent.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
+
+ Intent connectionAccessIntent = new Intent(action);
+ connectionAccessIntent.setClass(context, BluetoothPermissionActivity.class);
+ connectionAccessIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, requestType);
+ connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, returnPackage);
+ connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, returnClass);
+
+ String deviceAddress = device != null ? device.getAddress() : null;
+
+ PowerManager powerManager =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+
+ if (powerManager.isScreenOn() &&
+ LocalBluetoothPreferences.shouldShowDialogInForeground(context, deviceAddress) ) {
+ context.startActivity(connectionAccessIntent);
+ } else {
+ // Put up a notification that leads to the dialog
+
+ // Create an intent triggered by clicking on the
+ // "Clear All Notifications" button
+
+ Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
+ deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+ BluetoothDevice.CONNECTION_ACCESS_NO);
+
+ Notification notification = new Notification(android.R.drawable.stat_sys_data_bluetooth,
+ context.getString(R.string.bluetooth_connection_permission_request),
+ System.currentTimeMillis());
+ String deviceName = device != null ? device.getAliasName() : null;
+ notification.setLatestEventInfo(context,
+ context.getString(R.string.bluetooth_connection_permission_request),
+ context.getString(R.string.bluetooth_connection_notif_message, deviceName),
+ PendingIntent.getActivity(context, 0, connectionAccessIntent, 0));
+ notification.flags = Notification.FLAG_AUTO_CANCEL |
+ Notification.FLAG_ONLY_ALERT_ONCE;
+ notification.defaults = Notification.DEFAULT_SOUND;
+ notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
+
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(NOTIFICATION_ID, notification);
+ }
+ } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
+ // Remove the notification
+ NotificationManager manager = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.cancel(NOTIFICATION_ID);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothProfilePreference.java b/src/com/android/settings/bluetooth/BluetoothProfilePreference.java
deleted file mode 100644
index 8f6d0a2..0000000
--- a/src/com/android/settings/bluetooth/BluetoothProfilePreference.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.bluetooth;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.preference.Preference;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageView;
-
-import com.android.settings.R;
-
-/**
- * BluetoothProfilePreference is the preference type used to display each profile for a
- * particular bluetooth device.
- */
-final class BluetoothProfilePreference extends Preference implements OnClickListener {
-
-// private static final String TAG = "BluetoothProfilePreference";
-
- private Drawable mProfileDrawable;
- private boolean mExpanded;
- private ImageView mProfileExpandView;
- private final LocalBluetoothProfile mProfile;
-
- private OnClickListener mOnExpandClickListener;
-
- BluetoothProfilePreference(Context context, LocalBluetoothProfile profile) {
- super(context);
-
- mProfile = profile;
-
- setWidgetLayoutResource(R.layout.preference_bluetooth_profile);
- setExpanded(false);
- }
-
- public void setOnExpandClickListener(OnClickListener listener) {
- mOnExpandClickListener = listener;
- }
-
- public void setExpanded(boolean expanded) {
- mExpanded = expanded;
- notifyChanged();
- }
-
- public boolean isExpanded() {
- return mExpanded;
- }
-
- public void setProfileDrawable(Drawable drawable) {
- mProfileDrawable = drawable;
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
-
- ImageView btProfile = (ImageView) view.findViewById(android.R.id.icon);
- btProfile.setImageDrawable(mProfileDrawable);
-
- mProfileExpandView = (ImageView) view.findViewById(R.id.profileExpand);
- if (mProfile.isAutoConnectable()) {
- mProfileExpandView.setOnClickListener(this);
- mProfileExpandView.setTag(mProfile);
- mProfileExpandView.setImageResource(mExpanded
- ? com.android.internal.R.drawable.expander_close_holo_dark
- : com.android.internal.R.drawable.expander_open_holo_dark);
- } else {
- mProfileExpandView.setVisibility(View.GONE);
- }
- }
-
- public void onClick(View v) {
- if (v == mProfileExpandView) {
- if (mOnExpandClickListener != null) {
- setExpanded(!mExpanded);
- mOnExpandClickListener.onClick(v);
- }
- }
- }
-}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index 5e4e130..af21149 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -16,16 +16,32 @@
package com.android.settings.bluetooth;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
+import android.content.IntentFilter;
+import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Switch;
+import android.widget.TextView;
+import com.android.settings.ProgressCategory;
import com.android.settings.R;
/**
@@ -35,107 +51,310 @@ import com.android.settings.R;
public final class BluetoothSettings extends DeviceListPreferenceFragment {
private static final String TAG = "BluetoothSettings";
- private static final String KEY_BT_CHECKBOX = "bt_checkbox";
- private static final String KEY_BT_DISCOVERABLE = "bt_discoverable";
- private static final String KEY_BT_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout";
- private static final String KEY_BT_NAME = "bt_name";
- private static final String KEY_BT_SHOW_RECEIVED = "bt_show_received_files";
-
- private BluetoothEnabler mEnabler;
- private BluetoothDiscoverableEnabler mDiscoverableEnabler;
- private BluetoothNamePreference mNamePreference;
+ private static final int MENU_ID_SCAN = Menu.FIRST;
+ private static final int MENU_ID_RENAME_DEVICE = Menu.FIRST + 1;
+ private static final int MENU_ID_VISIBILITY_TIMEOUT = Menu.FIRST + 2;
+ private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 3;
/* Private intent to show the list of received files */
private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
"android.btopp.intent.action.OPEN_RECEIVED_FILES";
- /** Initialize the filter to show bonded devices only. */
+ private BluetoothEnabler mBluetoothEnabler;
+
+ private BluetoothDiscoverableEnabler mDiscoverableEnabler;
+
+ private PreferenceGroup mPairedDevicesCategory;
+
+ private PreferenceGroup mAvailableDevicesCategory;
+ private boolean mAvailableDevicesCategoryIsPresent;
+
+ private View mView;
+ private TextView mEmptyView;
+
+ private final IntentFilter mIntentFilter;
+
+ // accessed from inner class (not private to avoid thunks)
+ Preference mMyDevicePreference;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
+ updateDeviceName();
+ }
+ }
+
+ private void updateDeviceName() {
+ if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) {
+ mMyDevicePreference.setTitle(mLocalAdapter.getName());
+ }
+ }
+ };
+
public BluetoothSettings() {
- super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
+ mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mView = inflater.inflate(R.layout.custom_preference_list_fragment, container, false);
+ return mView;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ mEmptyView = (TextView) mView.findViewById(R.id.empty);
+ getListView().setEmptyView(mEmptyView);
}
@Override
void addPreferencesForActivity() {
addPreferencesFromResource(R.xml.bluetooth_settings);
- mEnabler = new BluetoothEnabler(getActivity(),
- (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX));
+ Activity activity = getActivity();
+
+ Switch actionBarSwitch = new Switch(activity);
- mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),
- mLocalAdapter,
- (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE),
- (ListPreference) findPreference(KEY_BT_DISCOVERABLE_TIMEOUT));
+ if (activity instanceof PreferenceActivity) {
+ PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
+ final int padding = activity.getResources().getDimensionPixelSize(
+ R.dimen.action_bar_switch_padding);
+ actionBarSwitch.setPadding(0, 0, padding, 0);
+ activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM);
+ activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT));
+ }
+ }
+
+ mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch);
- mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
+ setHasOptionsMenu(true);
}
@Override
public void onResume() {
+ // resume BluetoothEnabler before calling super.onResume() so we don't get
+ // any onDeviceAdded() callbacks before setting up view in updateContent()
+ mBluetoothEnabler.resume();
super.onResume();
- // Repopulate (which isn't too bad since it's cached in the settings
- // bluetooth manager)
- addDevices();
+ if (mDiscoverableEnabler != null) {
+ mDiscoverableEnabler.resume();
+ }
+ getActivity().registerReceiver(mReceiver, mIntentFilter);
- mEnabler.resume();
- mDiscoverableEnabler.resume();
- mNamePreference.resume();
+ updateContent(mLocalAdapter.getBluetoothState());
}
@Override
public void onPause() {
super.onPause();
+ mBluetoothEnabler.pause();
+ getActivity().unregisterReceiver(mReceiver);
+ if (mDiscoverableEnabler != null) {
+ mDiscoverableEnabler.pause();
+ }
+ }
- mNamePreference.pause();
- mDiscoverableEnabler.pause();
- mEnabler.pause();
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON;
+ boolean isDiscovering = mLocalAdapter.isDiscovering();
+ int textId = isDiscovering ? R.string.bluetooth_searching_for_devices :
+ R.string.bluetooth_search_for_devices;
+ menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId)
+ .setEnabled(bluetoothIsEnabled && !isDiscovering)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device)
+ .setEnabled(bluetoothIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ menu.add(Menu.NONE, MENU_ID_VISIBILITY_TIMEOUT, 0, R.string.bluetooth_visibility_timeout)
+ .setEnabled(bluetoothIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
- private final View.OnClickListener mListener = new View.OnClickListener() {
- public void onClick(View v) {
- // User clicked on advanced options icon for a device in the list
- if (v.getTag() instanceof CachedBluetoothDevice) {
- CachedBluetoothDevice
- device = (CachedBluetoothDevice) v.getTag();
-
- Preference pref = new Preference(getActivity());
- pref.setTitle(device.getName());
- pref.setFragment(DeviceProfilesSettings.class.getName());
- pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE,
- device.getDevice());
- ((PreferenceActivity) getActivity())
- .onPreferenceStartFragment(BluetoothSettings.this,
- pref);
- } else {
- Log.w(TAG, "onClick() called for other View: " + v);
- }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ID_SCAN:
+ if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
+ startScanning();
+ }
+ return true;
+
+ case MENU_ID_RENAME_DEVICE:
+ new BluetoothNameDialogFragment(mLocalAdapter).show(
+ getFragmentManager(), "rename device");
+ return true;
+
+ case MENU_ID_VISIBILITY_TIMEOUT:
+ new BluetoothVisibilityTimeoutFragment(mDiscoverableEnabler).show(
+ getFragmentManager(), "visibility timeout");
+ return true;
+
+ case MENU_ID_SHOW_RECEIVED:
+ Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
+ getActivity().sendBroadcast(intent);
+ return true;
}
- };
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void startScanning() {
+ if (!mAvailableDevicesCategoryIsPresent) {
+ getPreferenceScreen().addPreference(mAvailableDevicesCategory);
+ }
+ mLocalAdapter.startScanning(true);
+ }
@Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- if (KEY_BT_SHOW_RECEIVED.equals(preference.getKey())) {
- Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
- getActivity().sendBroadcast(intent);
- return true;
+ void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
+ mLocalAdapter.stopScanning();
+ super.onDevicePreferenceClick(btPreference);
+ }
+
+ private void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId,
+ BluetoothDeviceFilter.Filter filter) {
+ preferenceGroup.setTitle(titleId);
+ getPreferenceScreen().addPreference(preferenceGroup);
+ setFilter(filter);
+ setDeviceListGroup(preferenceGroup);
+ addCachedDevices();
+ preferenceGroup.setEnabled(true);
+ }
+
+ private void updateContent(int bluetoothState) {
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ int messageId = 0;
+
+ switch (bluetoothState) {
+ case BluetoothAdapter.STATE_ON:
+ preferenceScreen.removeAll();
+ preferenceScreen.setOrderingAsAdded(true);
+
+ // This device
+ if (mMyDevicePreference == null) {
+ mMyDevicePreference = new Preference(getActivity());
+ }
+ mMyDevicePreference.setTitle(mLocalAdapter.getName());
+ if (getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
+ mMyDevicePreference.setIcon(R.drawable.ic_bt_cellphone); // for phones
+ } else {
+ mMyDevicePreference.setIcon(R.drawable.ic_bt_laptop); // for tablets, etc.
+ }
+ mMyDevicePreference.setPersistent(false);
+ mMyDevicePreference.setEnabled(true);
+ preferenceScreen.addPreference(mMyDevicePreference);
+
+ if (mDiscoverableEnabler == null) {
+ mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),
+ mLocalAdapter, mMyDevicePreference);
+ mDiscoverableEnabler.resume();
+ }
+
+ // Paired devices category
+ if (mPairedDevicesCategory == null) {
+ mPairedDevicesCategory = new PreferenceCategory(getActivity());
+ } else {
+ mPairedDevicesCategory.removeAll();
+ }
+ addDeviceCategory(mPairedDevicesCategory,
+ R.string.bluetooth_preference_paired_devices,
+ BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
+ int numberOfPairedDevices = mPairedDevicesCategory.getPreferenceCount();
+
+ mDiscoverableEnabler.setNumberOfPairedDevices(numberOfPairedDevices);
+
+ // Available devices category
+ if (mAvailableDevicesCategory == null) {
+ mAvailableDevicesCategory = new ProgressCategory(getActivity(), null);
+ } else {
+ mAvailableDevicesCategory.removeAll();
+ }
+ addDeviceCategory(mAvailableDevicesCategory,
+ R.string.bluetooth_preference_found_devices,
+ BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
+ int numberOfAvailableDevices = mAvailableDevicesCategory.getPreferenceCount();
+ mAvailableDevicesCategoryIsPresent = true;
+
+ if (numberOfAvailableDevices == 0) {
+ preferenceScreen.removePreference(mAvailableDevicesCategory);
+ mAvailableDevicesCategoryIsPresent = false;
+ }
+
+ if (numberOfPairedDevices == 0) {
+ preferenceScreen.removePreference(mPairedDevicesCategory);
+ startScanning();
+ }
+ getActivity().invalidateOptionsMenu();
+ return; // not break
+
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ messageId = R.string.bluetooth_turning_off;
+ break;
+
+ case BluetoothAdapter.STATE_OFF:
+ messageId = R.string.bluetooth_empty_list_bluetooth_off;
+ break;
+
+ case BluetoothAdapter.STATE_TURNING_ON:
+ messageId = R.string.bluetooth_turning_on;
+ break;
}
- return super.onPreferenceTreeClick(preferenceScreen, preference);
+ setDeviceListGroup(preferenceScreen);
+ removeAllDevices();
+ mEmptyView.setText(messageId);
+ getActivity().invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ super.onBluetoothStateChanged(bluetoothState);
+ updateContent(bluetoothState);
+ }
+
+ @Override
+ public void onScanningStateChanged(boolean started) {
+ super.onScanningStateChanged(started);
+ // Update options' enabled state
+ getActivity().invalidateOptionsMenu();
+ }
+
+ public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+ setDeviceListGroup(getPreferenceScreen());
+ removeAllDevices();
+ updateContent(mLocalAdapter.getBluetoothState());
}
- public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice,
- int bondState) {
- if (bondState == BluetoothDevice.BOND_BONDED) {
- // add to "Paired devices" list after remote-initiated pairing
- if (mDevicePreferenceMap.get(cachedDevice) == null) {
- createDevicePreference(cachedDevice);
+ private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ // User clicked on advanced options icon for a device in the list
+ if (v.getTag() instanceof CachedBluetoothDevice) {
+ CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
+
+ Bundle args = new Bundle(1);
+ args.putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, device.getDevice());
+
+ ((PreferenceActivity) getActivity()).startPreferencePanel(
+ DeviceProfilesSettings.class.getName(), args,
+ R.string.bluetooth_device_advanced_title, null, null, 0);
+ } else {
+ Log.w(TAG, "onClick() called for other View: " + v); // TODO remove
}
- } else if (bondState == BluetoothDevice.BOND_NONE) {
- // remove unpaired device from paired devices list
- onDeviceDeleted(cachedDevice);
}
- }
+ };
/**
* Add a listener, which enables the advanced settings icon.
@@ -143,6 +362,10 @@ public final class BluetoothSettings extends DeviceListPreferenceFragment {
*/
@Override
void initDevicePreference(BluetoothDevicePreference preference) {
- preference.setOnSettingsClickListener(mListener);
+ CachedBluetoothDevice cachedDevice = preference.getCachedDevice();
+ if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
+ // Only paired device have an associated advanced settings screen
+ preference.setOnSettingsClickListener(mDeviceProfilesListener);
+ }
}
}
diff --git a/src/com/android/settings/bluetooth/BluetoothVisibilityTimeoutFragment.java b/src/com/android/settings/bluetooth/BluetoothVisibilityTimeoutFragment.java
new file mode 100644
index 0000000..7c518fb
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothVisibilityTimeoutFragment.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.android.internal.app.AlertController;
+import com.android.settings.R;
+
+/**
+ * Dialog fragment for setting the discoverability timeout.
+ */
+final class BluetoothVisibilityTimeoutFragment extends DialogFragment
+ implements DialogInterface.OnClickListener {
+
+ private final BluetoothDiscoverableEnabler mDiscoverableEnabler;
+
+ public BluetoothVisibilityTimeoutFragment(BluetoothDiscoverableEnabler enabler) {
+ mDiscoverableEnabler = enabler;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.bluetooth_visibility_timeout)
+ .setSingleChoiceItems(R.array.bluetooth_visibility_timeout_entries,
+ mDiscoverableEnabler.getDiscoverableTimeoutIndex(), this)
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ mDiscoverableEnabler.setDiscoverableTimeout(which);
+ dismiss();
+ }
+}
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
index 71a5c01..8082314 100644
--- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
@@ -257,6 +257,14 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
return true;
}
+ /**
+ * Return true if user initiated pairing on this device. The message text is
+ * slightly different for local vs. remote initiated pairing dialogs.
+ */
+ boolean isUserInitiatedPairing() {
+ return mConnectAfterPairing;
+ }
+
void unpair() {
disconnect();
@@ -318,8 +326,8 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
mName = mDevice.getAddress();
} else {
mName = name;
+ mDevice.setAlias(name);
}
- // TODO: save custom device name in preferences
dispatchAttributesChanged();
}
}
@@ -330,7 +338,7 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
}
private void fetchName() {
- mName = mDevice.getName();
+ mName = mDevice.getAliasName();
if (TextUtils.isEmpty(mName)) {
mName = mDevice.getAddress();
@@ -414,7 +422,7 @@ final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles);
if (DEBUG) {
- Log.e(TAG, "updating profiles for " + mDevice.getName());
+ Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
index ab71ece..b511cb3 100644
--- a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
@@ -26,7 +26,6 @@ import java.util.List;
* CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
*/
final class CachedBluetoothDeviceManager {
-// private static final String TAG = "CachedBluetoothDeviceManager";
private final List<CachedBluetoothDevice> mCachedDevices =
new ArrayList<CachedBluetoothDevice>();
@@ -35,20 +34,9 @@ final class CachedBluetoothDeviceManager {
return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
}
- public boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
+ public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
cachedDevice.setVisible(false);
- return checkForDeviceRemoval(cachedDevice);
- }
-
- private boolean checkForDeviceRemoval(
- CachedBluetoothDevice cachedDevice) {
- if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&
- !cachedDevice.isVisible()) {
- // If device isn't paired, remove it altogether
- mCachedDevices.remove(cachedDevice);
- return true; // dispatch device deleted
- }
- return false;
+ return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
}
public void onDeviceNameUpdated(BluetoothDevice device) {
@@ -104,7 +92,7 @@ final class CachedBluetoothDeviceManager {
return cachedDevice.getName();
}
- String name = device.getName();
+ String name = device.getAliasName();
if (name != null) {
return name;
}
@@ -120,7 +108,6 @@ final class CachedBluetoothDeviceManager {
for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
cachedDevice.setVisible(false);
- checkForDeviceRemoval(cachedDevice);
}
}
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
index a978e23..061f2c9 100644
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceCategory;
+import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Log;
@@ -36,7 +37,6 @@ import java.util.WeakHashMap;
*
* @see BluetoothSettings
* @see DevicePickerFragment
- * @see BluetoothFindNearby
*/
public abstract class DeviceListPreferenceFragment extends
SettingsPreferenceFragment implements BluetoothCallback {
@@ -53,7 +53,7 @@ public abstract class DeviceListPreferenceFragment extends
LocalBluetoothAdapter mLocalAdapter;
LocalBluetoothManager mLocalManager;
- private PreferenceCategory mDeviceList;
+ private PreferenceGroup mDeviceListGroup;
final WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>();
@@ -62,7 +62,7 @@ public abstract class DeviceListPreferenceFragment extends
mFilter = BluetoothDeviceFilter.ALL_FILTER;
}
- DeviceListPreferenceFragment(BluetoothDeviceFilter.Filter filter) {
+ final void setFilter(BluetoothDeviceFilter.Filter filter) {
mFilter = filter;
}
@@ -83,10 +83,11 @@ public abstract class DeviceListPreferenceFragment extends
addPreferencesForActivity();
- mDeviceList = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST);
- if (mDeviceList == null) {
- Log.e(TAG, "Could not find device list preference object!");
- }
+ mDeviceListGroup = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST);
+ }
+
+ void setDeviceListGroup(PreferenceGroup preferenceGroup) {
+ mDeviceListGroup = preferenceGroup;
}
/** Add preferences from the subclass. */
@@ -105,16 +106,18 @@ public abstract class DeviceListPreferenceFragment extends
@Override
public void onPause() {
super.onPause();
-
- mLocalAdapter.stopScanning();
+ removeAllDevices();
mLocalManager.setForegroundActivity(null);
mLocalManager.getEventManager().unregisterCallback(this);
+ }
+ void removeAllDevices() {
+ mLocalAdapter.stopScanning();
mDevicePreferenceMap.clear();
- mDeviceList.removeAll();
+ mDeviceListGroup.removeAll();
}
- void addDevices() {
+ void addCachedDevices() {
Collection<CachedBluetoothDevice> cachedDevices =
mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
for (CachedBluetoothDevice cachedDevice : cachedDevices) {
@@ -125,14 +128,13 @@ public abstract class DeviceListPreferenceFragment extends
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
Preference preference) {
-
if (KEY_BT_SCAN.equals(preference.getKey())) {
mLocalAdapter.startScanning(true);
return true;
}
if (preference instanceof BluetoothDevicePreference) {
- BluetoothDevicePreference btPreference = (BluetoothDevicePreference)preference;
+ BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
CachedBluetoothDevice device = btPreference.getCachedDevice();
mSelectedDevice = device.getDevice();
onDevicePreferenceClick(btPreference);
@@ -148,10 +150,12 @@ public abstract class DeviceListPreferenceFragment extends
public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
if (mDevicePreferenceMap.get(cachedDevice) != null) {
- Log.e(TAG, "Got onDeviceAdded, but cachedDevice already exists");
return;
}
+ // Prevent updates while the list shows one of the state messages
+ if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;
+
if (mFilter.matches(cachedDevice.getDevice())) {
createDevicePreference(cachedDevice);
}
@@ -162,7 +166,7 @@ public abstract class DeviceListPreferenceFragment extends
getActivity(), cachedDevice);
initDevicePreference(preference);
- mDeviceList.addPreference(preference);
+ mDeviceListGroup.addPreference(preference);
mDevicePreferenceMap.put(cachedDevice, preference);
}
@@ -170,13 +174,14 @@ public abstract class DeviceListPreferenceFragment extends
* Overridden in {@link BluetoothSettings} to add a listener.
* @param preference the newly added preference
*/
- void initDevicePreference(BluetoothDevicePreference preference) { }
+ void initDevicePreference(BluetoothDevicePreference preference) {
+ // Does nothing by default
+ }
public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
- BluetoothDevicePreference preference = mDevicePreferenceMap.remove(
- cachedDevice);
+ BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
if (preference != null) {
- mDeviceList.removePreference(preference);
+ mDeviceListGroup.removePreference(preference);
}
}
@@ -185,8 +190,8 @@ public abstract class DeviceListPreferenceFragment extends
}
private void updateProgressUi(boolean start) {
- if (mDeviceList instanceof ProgressCategory) {
- ((ProgressCategory) mDeviceList).setProgress(start);
+ if (mDeviceListGroup instanceof ProgressCategory) {
+ ((ProgressCategory) mDeviceListGroup).setProgress(start);
}
}
diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java
index 126df02..8b32941 100644
--- a/src/com/android/settings/bluetooth/DevicePickerFragment.java
+++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java
@@ -55,7 +55,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment {
@Override
public void onResume() {
super.onResume();
- addDevices();
+ addCachedDevices();
mLocalAdapter.startScanning(true);
}
@@ -89,7 +89,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment {
super.onBluetoothStateChanged(bluetoothState);
if (bluetoothState == BluetoothAdapter.STATE_ON) {
- mLocalAdapter.startScanning(false);
+ mLocalAdapter.startScanning(false);
}
}
diff --git a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
index 9db4baf..a840982 100644..100755
--- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
+++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,10 +27,15 @@ import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
+import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
-
+import android.widget.EditText;
+import android.text.TextWatcher;
+import android.app.Dialog;
+import android.widget.Button;
+import android.text.Editable;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
@@ -42,18 +47,15 @@ import java.util.HashMap;
* (or disconnected).
*/
public final class DeviceProfilesSettings extends SettingsPreferenceFragment
- implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener,
- View.OnClickListener {
+ implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
private static final String TAG = "DeviceProfilesSettings";
- private static final String KEY_TITLE = "title";
private static final String KEY_RENAME_DEVICE = "rename_device";
private static final String KEY_PROFILE_CONTAINER = "profile_container";
private static final String KEY_UNPAIR = "unpair";
- private static final String KEY_ALLOW_INCOMING = "allow_incoming";
public static final String EXTRA_DEVICE = "device";
-
+ private RenameEditTextPreference mRenameDeviceNamePref;
private LocalBluetoothManager mManager;
private CachedBluetoothDevice mCachedDevice;
private LocalBluetoothProfileManager mProfileManager;
@@ -65,6 +67,26 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
= new HashMap<LocalBluetoothProfile, CheckBoxPreference>();
private AlertDialog mDisconnectDialog;
+ private boolean mProfileGroupIsRemoved;
+
+ private class RenameEditTextPreference implements TextWatcher{
+ public void afterTextChanged(Editable s) {
+ Dialog d = mDeviceNamePref.getDialog();
+ if (d instanceof AlertDialog) {
+ ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0);
+ }
+ }
+
+ // TextWatcher interface
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // not used
+ }
+
+ // TextWatcher interface
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // not used
+ }
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -88,7 +110,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
finish();
return; // TODO: test this failure path
}
-
+ mRenameDeviceNamePref = new RenameEditTextPreference();
mManager = LocalBluetoothManager.getInstance(getActivity());
CachedBluetoothDeviceManager deviceManager =
mManager.getCachedDeviceManager();
@@ -105,11 +127,6 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mDeviceNamePref.setText(deviceName);
mDeviceNamePref.setOnPreferenceChangeListener(this);
- // Set the title of the screen
- findPreference(KEY_TITLE).setTitle(
- getString(R.string.bluetooth_device_advanced_title,
- deviceName));
-
// Add a preference for each profile
addPreferencesForProfiles();
}
@@ -135,8 +152,18 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mManager.setForegroundActivity(getActivity());
mCachedDevice.registerCallback(this);
-
+ if(mCachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
+ finish();
refresh();
+ EditText et = mDeviceNamePref.getEditText();
+ if (et != null) {
+ et.addTextChangedListener(mRenameDeviceNamePref);
+ Dialog d = mDeviceNamePref.getDialog();
+ if (d instanceof AlertDialog) {
+ Button b = ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE);
+ b.setEnabled(et.getText().length() > 0);
+ }
+ }
}
@Override
@@ -152,6 +179,18 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
Preference pref = createProfilePreference(profile);
mProfileContainer.addPreference(pref);
}
+ showOrHideProfileGroup();
+ }
+
+ private void showOrHideProfileGroup() {
+ int numProfiles = mProfileContainer.getPreferenceCount();
+ if (!mProfileGroupIsRemoved && numProfiles == 0) {
+ getPreferenceScreen().removePreference(mProfileContainer);
+ mProfileGroupIsRemoved = true;
+ } else if (mProfileGroupIsRemoved && numProfiles != 0) {
+ getPreferenceScreen().addPreference(mProfileContainer);
+ mProfileGroupIsRemoved = false;
+ }
}
/**
@@ -162,18 +201,17 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
* @return A preference that allows the user to choose whether this profile
* will be connected to.
*/
- private Preference createProfilePreference(LocalBluetoothProfile profile) {
- BluetoothProfilePreference pref = new BluetoothProfilePreference(getActivity(), profile);
+ private CheckBoxPreference createProfilePreference(LocalBluetoothProfile profile) {
+ CheckBoxPreference pref = new CheckBoxPreference(getActivity());
pref.setKey(profile.toString());
- pref.setTitle(profile.getNameResource());
- pref.setExpanded(false);
+ pref.setTitle(profile.getNameResource(mCachedDevice.getDevice()));
pref.setPersistent(false);
pref.setOrder(getProfilePreferenceIndex(profile.getOrdinal()));
- pref.setOnExpandClickListener(this);
+ pref.setOnPreferenceChangeListener(this);
- int iconResource = profile.getDrawableResource(null); // FIXME: get BT class for this?
+ int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass());
if (iconResource != 0) {
- pref.setProfileDrawable(getResources().getDrawable(iconResource));
+ pref.setIcon(getResources().getDrawable(iconResource));
}
/**
@@ -189,10 +227,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
@Override
public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
String key = preference.getKey();
- if (preference instanceof BluetoothProfilePreference) {
- onProfileClicked(mProfileManager.getProfileByName(key));
- return true;
- } else if (key.equals(KEY_UNPAIR)) {
+ if (key.equals(KEY_UNPAIR)) {
unpairDevice();
finish();
return true;
@@ -205,11 +240,9 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
if (preference == mDeviceNamePref) {
mCachedDevice.setName((String) newValue);
} else if (preference instanceof CheckBoxPreference) {
- boolean autoConnect = (Boolean) newValue;
LocalBluetoothProfile prof = getProfileOf(preference);
- prof.setPreferred(mCachedDevice.getDevice(),
- autoConnect);
- return true;
+ onProfileClicked(prof);
+ return false; // checkbox will update from onDeviceAttributesChanged() callback
} else {
return false;
}
@@ -227,6 +260,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
if (isConnected) {
askDisconnect(getActivity(), profile);
} else {
+ profile.setPreferred(device, true);
mCachedDevice.connectProfile(profile);
}
}
@@ -239,22 +273,23 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
if (TextUtils.isEmpty(name)) {
name = context.getString(R.string.bluetooth_device);
}
- int disconnectMessage = profile.getDisconnectResource(device.getDevice());
- if (disconnectMessage == 0) {
- Log.w(TAG, "askDisconnect: unexpected profile " + profile);
- disconnectMessage = R.string.bluetooth_disconnect_blank;
- }
- String message = context.getString(disconnectMessage, name);
+
+ String profileName = context.getString(profile.getNameResource(device.getDevice()));
+
+ String title = context.getString(R.string.bluetooth_disable_profile_title);
+ String message = context.getString(R.string.bluetooth_disable_profile_message,
+ profileName, name);
DialogInterface.OnClickListener disconnectListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
device.disconnect(profile);
+ profile.setPreferred(device.getDevice(), false);
}
};
mDisconnectDialog = Utils.showDisconnectDialog(context,
- mDisconnectDialog, disconnectListener, name, message);
+ mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
}
public void onDeviceAttributesChanged() {
@@ -263,15 +298,6 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
private void refresh() {
String deviceName = mCachedDevice.getName();
- // TODO: figure out how to update "bread crumb" title in action bar
-// FragmentTransaction transaction = getFragmentManager().openTransaction();
-// transaction.setBreadCrumbTitle(deviceName);
-// transaction.commit();
-
- findPreference(KEY_TITLE).setTitle(getString(
- R.string.bluetooth_device_advanced_title,
- deviceName));
- mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE);
mDeviceNamePref.setSummary(deviceName);
mDeviceNamePref.setText(deviceName);
@@ -280,7 +306,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
private void refreshProfiles() {
for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
- Preference profilePref = findPreference(profile.toString());
+ CheckBoxPreference profilePref = (CheckBoxPreference)findPreference(profile.toString());
if (profilePref == null) {
profilePref = createProfilePreference(profile);
mProfileContainer.addPreference(profilePref);
@@ -295,15 +321,18 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mProfileContainer.removePreference(profilePref);
}
}
+ showOrHideProfileGroup();
}
- private void refreshProfilePreference(Preference profilePref, LocalBluetoothProfile profile) {
+ private void refreshProfilePreference(CheckBoxPreference profilePref,
+ LocalBluetoothProfile profile) {
BluetoothDevice device = mCachedDevice.getDevice();
/*
* Gray out checkbox while connecting and disconnecting
*/
profilePref.setEnabled(!mCachedDevice.isBusy());
+ profilePref.setChecked(mCachedDevice.isConnectedProfile(profile));
profilePref.setSummary(profile.getSummaryResourceForDevice(device));
}
@@ -321,32 +350,6 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
}
}
- public void onClick(View v) {
- if (v.getTag() instanceof LocalBluetoothProfile) {
- LocalBluetoothProfile prof = (LocalBluetoothProfile) v.getTag();
- CheckBoxPreference autoConnectPref = mAutoConnectPrefs.get(prof);
- if (autoConnectPref == null) {
- autoConnectPref = new CheckBoxPreference(getActivity());
- autoConnectPref.setLayoutResource(com.android.internal.R.layout.preference_child);
- autoConnectPref.setKey(prof.toString());
- autoConnectPref.setTitle(R.string.bluetooth_auto_connect);
- autoConnectPref.setOrder(getProfilePreferenceIndex(prof.getOrdinal()) + 1);
- autoConnectPref.setChecked(getAutoConnect(prof));
- autoConnectPref.setOnPreferenceChangeListener(this);
- mAutoConnectPrefs.put(prof, autoConnectPref);
- }
- BluetoothProfilePreference profilePref =
- (BluetoothProfilePreference) findPreference(prof.toString());
- if (profilePref != null) {
- if (profilePref.isExpanded()) {
- mProfileContainer.addPreference(autoConnectPref);
- } else {
- mProfileContainer.removePreference(autoConnectPref);
- }
- }
- }
- }
-
private int getProfilePreferenceIndex(int profIndex) {
return mProfileContainer.getOrder() + profIndex * 10;
}
@@ -355,16 +358,6 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mCachedDevice.unpair();
}
- private void setIncomingFileTransfersAllowed(boolean allow) {
- // TODO: make an IPC call into BluetoothOpp to update
- Log.d(TAG, "Set allow incoming = " + allow);
- }
-
- private boolean isIncomingFileTransfersAllowed() {
- // TODO: get this value from BluetoothOpp ???
- return true;
- }
-
private boolean getAutoConnect(LocalBluetoothProfile prof) {
return prof.isPreferred(mCachedDevice.getDevice());
}
diff --git a/src/com/android/settings/bluetooth/DockEventReceiver.java b/src/com/android/settings/bluetooth/DockEventReceiver.java
index 6ecbef6..d18348f 100644
--- a/src/com/android/settings/bluetooth/DockEventReceiver.java
+++ b/src/com/android/settings/bluetooth/DockEventReceiver.java
@@ -54,7 +54,7 @@ public final class DockEventReceiver extends BroadcastReceiver {
if (DEBUG) {
Log.d(TAG, "Action: " + intent.getAction() + " State:" + state + " Device: "
- + (device == null ? "null" : device.getName()));
+ + (device == null ? "null" : device.getAliasName()));
}
if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())
diff --git a/src/com/android/settings/bluetooth/DockService.java b/src/com/android/settings/bluetooth/DockService.java
index b457706..f0644c3 100644
--- a/src/com/android/settings/bluetooth/DockService.java
+++ b/src/com/android/settings/bluetooth/DockService.java
@@ -424,7 +424,7 @@ public final class DockService extends Service implements ServiceListener {
if (DEBUG) {
Log.d(TAG, "Action: " + intent.getAction() + " State:" + state
- + " Device: " + (device == null ? "null" : device.getName()));
+ + " Device: " + (device == null ? "null" : device.getAliasName()));
}
if (device == null) {
diff --git a/src/com/android/settings/bluetooth/HeadsetProfile.java b/src/com/android/settings/bluetooth/HeadsetProfile.java
index 13dce33..99d070b 100644
--- a/src/com/android/settings/bluetooth/HeadsetProfile.java
+++ b/src/com/android/settings/bluetooth/HeadsetProfile.java
@@ -170,14 +170,10 @@ final class HeadsetProfile implements LocalBluetoothProfile {
return ORDINAL;
}
- public int getNameResource() {
+ public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_headset;
}
- public int getDisconnectResource(BluetoothDevice device) {
- return R.string.bluetooth_disconnect_headset_profile;
- }
-
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = mService.getConnectionState(device);
switch (state) {
diff --git a/src/com/android/settings/bluetooth/HidProfile.java b/src/com/android/settings/bluetooth/HidProfile.java
index 13d3db9..920f4bb 100644
--- a/src/com/android/settings/bluetooth/HidProfile.java
+++ b/src/com/android/settings/bluetooth/HidProfile.java
@@ -112,14 +112,11 @@ final class HidProfile implements LocalBluetoothProfile {
return ORDINAL;
}
- public int getNameResource() {
+ public int getNameResource(BluetoothDevice device) {
+ // TODO: distinguish between keyboard and mouse?
return R.string.bluetooth_profile_hid;
}
- public int getDisconnectResource(BluetoothDevice device) {
- return R.string.bluetooth_disconnect_hid_profile;
- }
-
public int getSummaryResourceForDevice(BluetoothDevice device) {
int state = mService.getConnectionState(device);
switch (state) {
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
index 63b8b7c..a1edca1 100644..100755
--- a/src/com/android/settings/bluetooth/LocalBluetoothManager.java
+++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
@@ -79,6 +79,10 @@ public final class LocalBluetoothManager {
return mContext;
}
+ public Context getForegroundActivity() {
+ return mForegroundActivity;
+ }
+
boolean isForegroundActivity() {
return mForegroundActivity != null;
}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfile.java b/src/com/android/settings/bluetooth/LocalBluetoothProfile.java
index 878a032..8c0de95 100644
--- a/src/com/android/settings/bluetooth/LocalBluetoothProfile.java
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfile.java
@@ -54,15 +54,9 @@ interface LocalBluetoothProfile {
/**
* Returns the string resource ID for the localized name for this profile.
+ * @param device the Bluetooth device (to distinguish between PAN roles)
*/
- int getNameResource();
-
- /**
- * Returns the string resource ID for the disconnect confirmation text
- * for this profile.
- * @param device
- */
- int getDisconnectResource(BluetoothDevice device);
+ int getNameResource(BluetoothDevice device);
/**
* Returns the string resource ID for the summary text for this profile
diff --git a/src/com/android/settings/bluetooth/OppProfile.java b/src/com/android/settings/bluetooth/OppProfile.java
index eb5900e..7ee2ad1 100644
--- a/src/com/android/settings/bluetooth/OppProfile.java
+++ b/src/com/android/settings/bluetooth/OppProfile.java
@@ -75,14 +75,10 @@ final class OppProfile implements LocalBluetoothProfile {
return ORDINAL;
}
- public int getNameResource() {
+ public int getNameResource(BluetoothDevice device) {
return R.string.bluetooth_profile_opp;
}
- public int getDisconnectResource(BluetoothDevice device) {
- return 0; // user must use notification to disconnect OPP transfer.
- }
-
public int getSummaryResourceForDevice(BluetoothDevice device) {
return 0; // OPP profile not displayed in UI
}
diff --git a/src/com/android/settings/bluetooth/PanProfile.java b/src/com/android/settings/bluetooth/PanProfile.java
index 6cb1991..3db4a2b 100644
--- a/src/com/android/settings/bluetooth/PanProfile.java
+++ b/src/com/android/settings/bluetooth/PanProfile.java
@@ -112,15 +112,11 @@ final class PanProfile implements LocalBluetoothProfile {
return ORDINAL;
}
- public int getNameResource() {
- return R.string.bluetooth_profile_pan;
- }
-
- public int getDisconnectResource(BluetoothDevice device) {
+ public int getNameResource(BluetoothDevice device) {
if (isLocalRoleNap(device)) {
- return R.string.bluetooth_disconnect_pan_nap_profile;
+ return R.string.bluetooth_profile_pan_nap;
} else {
- return R.string.bluetooth_disconnect_pan_user_profile;
+ return R.string.bluetooth_profile_pan;
}
}
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
index 7d38e17..01e72e0 100644..100755
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -89,11 +89,17 @@ final class Utils {
static void showError(Context context, String name, int messageResId) {
String message = context.getString(messageResId, name);
- new AlertDialog.Builder(context)
+ LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context);
+ Context activity = manager.getForegroundActivity();
+ if(manager.isForegroundActivity()) {
+ new AlertDialog.Builder(activity)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.bluetooth_error_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
+ } else {
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
}
}
diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java
index db60c37..e257f86 100644
--- a/src/com/android/settings/deviceinfo/Memory.java
+++ b/src/com/android/settings/deviceinfo/Memory.java
@@ -34,8 +34,12 @@ import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.preference.Preference;
+import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.Toast;
import com.android.settings.R;
@@ -47,6 +51,8 @@ public class Memory extends SettingsPreferenceFragment {
private static final int DLG_CONFIRM_UNMOUNT = 1;
private static final int DLG_ERROR_UNMOUNT = 2;
+ private static final int MENU_ID_USB = Menu.FIRST;
+
private Resources mResources;
// The mountToggle Preference that has last been clicked.
@@ -60,6 +66,7 @@ public class Memory extends SettingsPreferenceFragment {
private StorageManager mStorageManager = null;
+ private StorageVolumePreferenceCategory mInternalStorageVolumePreferenceCategory;
private StorageVolumePreferenceCategory[] mStorageVolumePreferenceCategories;
@Override
@@ -75,18 +82,32 @@ public class Memory extends SettingsPreferenceFragment {
mResources = getResources();
+ if (!Environment.isExternalStorageEmulated()) {
+ // External storage is separate from internal storage; need to
+ // show internal storage as a separate item.
+ mInternalStorageVolumePreferenceCategory = new StorageVolumePreferenceCategory(
+ getActivity(), mResources, null, mStorageManager, false);
+ getPreferenceScreen().addPreference(mInternalStorageVolumePreferenceCategory);
+ mInternalStorageVolumePreferenceCategory.init();
+ }
+
StorageVolume[] storageVolumes = mStorageManager.getVolumeList();
+ // mass storage is enabled if primary volume supports it
+ boolean massStorageEnabled = (storageVolumes.length > 0
+ && storageVolumes[0].allowMassStorage());
int length = storageVolumes.length;
mStorageVolumePreferenceCategories = new StorageVolumePreferenceCategory[length];
for (int i = 0; i < length; i++) {
StorageVolume storageVolume = storageVolumes[i];
- StorageVolumePreferenceCategory storagePreferenceCategory =
- new StorageVolumePreferenceCategory(getActivity(), mResources, storageVolume,
- mStorageManager, i == 0); // The first volume is the primary volume
- mStorageVolumePreferenceCategories[i] = storagePreferenceCategory;
- getPreferenceScreen().addPreference(storagePreferenceCategory);
- storagePreferenceCategory.init();
+ boolean isPrimary = i == 0;
+ mStorageVolumePreferenceCategories[i] = new StorageVolumePreferenceCategory(
+ getActivity(), mResources, storageVolume, mStorageManager, isPrimary);
+ getPreferenceScreen().addPreference(mStorageVolumePreferenceCategories[i]);
+ mStorageVolumePreferenceCategories[i].init();
}
+
+ // only show options menu if we are not using the legacy USB mass storage support
+ setHasOptionsMenu(!massStorageEnabled);
}
@Override
@@ -97,6 +118,9 @@ public class Memory extends SettingsPreferenceFragment {
intentFilter.addDataScheme("file");
getActivity().registerReceiver(mMediaScannerReceiver, intentFilter);
+ if (mInternalStorageVolumePreferenceCategory != null) {
+ mInternalStorageVolumePreferenceCategory.onResume();
+ }
for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
mStorageVolumePreferenceCategories[i].onResume();
}
@@ -105,9 +129,8 @@ public class Memory extends SettingsPreferenceFragment {
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);
+ Log.i(TAG, "Received storage state changed notification that " + path +
+ " changed state from " + oldState + " to " + newState);
for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
StorageVolumePreferenceCategory svpc = mStorageVolumePreferenceCategories[i];
if (path.equals(svpc.getStorageVolume().getPath())) {
@@ -122,6 +145,9 @@ public class Memory extends SettingsPreferenceFragment {
public void onPause() {
super.onPause();
getActivity().unregisterReceiver(mMediaScannerReceiver);
+ if (mInternalStorageVolumePreferenceCategory != null) {
+ mInternalStorageVolumePreferenceCategory.onPause();
+ }
for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
mStorageVolumePreferenceCategories[i].onPause();
}
@@ -135,6 +161,31 @@ public class Memory extends SettingsPreferenceFragment {
super.onDestroy();
}
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(Menu.NONE, MENU_ID_USB, 0, R.string.storage_menu_usb)
+ //.setIcon(com.android.internal.R.drawable.stat_sys_data_usb)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ID_USB:
+ if (getActivity() instanceof PreferenceActivity) {
+ ((PreferenceActivity) getActivity()).startPreferencePanel(
+ UsbSettings.class.getCanonicalName(),
+ null,
+ R.string.storage_title_usb, null,
+ this, 0);
+ } else {
+ startFragment(this, UsbSettings.class.getCanonicalName(), -1, null);
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
private synchronized IMountService getMountService() {
if (mMountService == null) {
IBinder service = ServiceManager.getService("mount");
@@ -178,6 +229,7 @@ public class Memory extends SettingsPreferenceFragment {
private final BroadcastReceiver mMediaScannerReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ // mInternalStorageVolumePreferenceCategory is not affected by the media scanner
for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
mStorageVolumePreferenceCategories[i].onMediaScannerFinished();
}
diff --git a/src/com/android/settings/deviceinfo/PercentageBarChart.java b/src/com/android/settings/deviceinfo/PercentageBarChart.java
index 0c71c12..95973c4 100644
--- a/src/com/android/settings/deviceinfo/PercentageBarChart.java
+++ b/src/com/android/settings/deviceinfo/PercentageBarChart.java
@@ -71,20 +71,21 @@ public class PercentageBarChart extends View {
final int width = right - left;
- int lastX = left;
+ float lastX = left;
if (mEntries != null) {
for (final Entry e : mEntries) {
- final int entryWidth;
- if (e.percentage == 0f) {
- entryWidth = 0;
+ final float entryWidth;
+ if (e.percentage == 0.0f) {
+ entryWidth = 0.0f;
} else {
- entryWidth = Math.max(mMinTickWidth, (int) (width * e.percentage));
+ entryWidth = Math.max(mMinTickWidth, width * e.percentage);
}
- final int nextX = lastX + entryWidth;
- if (nextX >= right) {
- break;
+ final float nextX = lastX + entryWidth;
+ if (nextX > right) {
+ canvas.drawRect(lastX, top, right, bottom, e.paint);
+ return;
}
canvas.drawRect(lastX, top, nextX, bottom, e.paint);
@@ -92,7 +93,7 @@ public class PercentageBarChart extends View {
}
}
- canvas.drawRect(lastX, top, lastX + width, bottom, mEmptyPaint);
+ canvas.drawRect(lastX, top, right, bottom, mEmptyPaint);
}
/**
diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java
index 456bc98..987fab8 100644
--- a/src/com/android/settings/deviceinfo/Status.java
+++ b/src/com/android/settings/deviceinfo/Status.java
@@ -157,33 +157,8 @@ public class Status extends PreferenceActivity {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
-
- int level = intent.getIntExtra("level", 0);
- int scale = intent.getIntExtra("scale", 100);
-
- mBatteryLevel.setSummary(String.valueOf(level * 100 / scale) + "%");
-
- int plugType = intent.getIntExtra("plugged", 0);
- int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
- String statusString;
- if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
- statusString = getString(R.string.battery_info_status_charging);
- if (plugType > 0) {
- statusString = statusString + " " + getString(
- (plugType == BatteryManager.BATTERY_PLUGGED_AC)
- ? R.string.battery_info_status_charging_ac
- : R.string.battery_info_status_charging_usb);
- }
- } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
- statusString = getString(R.string.battery_info_status_discharging);
- } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
- statusString = getString(R.string.battery_info_status_not_charging);
- } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
- statusString = getString(R.string.battery_info_status_full);
- } else {
- statusString = getString(R.string.battery_info_status_unknown);
- }
- mBatteryStatus.setSummary(statusString);
+ mBatteryLevel.setSummary(Utils.getBatteryPercentage(intent));
+ mBatteryStatus.setSummary(Utils.getBatteryStatus(getResources(), intent));
}
}
};
diff --git a/src/com/android/settings/deviceinfo/StorageMeasurement.java b/src/com/android/settings/deviceinfo/StorageMeasurement.java
index 7fb309c..b4004e9 100644
--- a/src/com/android/settings/deviceinfo/StorageMeasurement.java
+++ b/src/com/android/settings/deviceinfo/StorageMeasurement.java
@@ -88,6 +88,7 @@ public class StorageMeasurement {
private static Map<StorageVolume, StorageMeasurement> sInstances =
new ConcurrentHashMap<StorageVolume, StorageMeasurement>();
+ private static StorageMeasurement sInternalInstance;
private volatile WeakReference<MeasurementReceiver> mReceiver;
@@ -100,6 +101,7 @@ public class StorageMeasurement {
final private StorageVolume mStorageVolume;
final private boolean mIsPrimary;
+ final private boolean mIsInternal;
List<FileInfo> mFileInfoForMisc;
@@ -110,7 +112,8 @@ public class StorageMeasurement {
private StorageMeasurement(Context context, StorageVolume storageVolume, boolean isPrimary) {
mStorageVolume = storageVolume;
- mIsPrimary = isPrimary;
+ mIsInternal = storageVolume == null;
+ mIsPrimary = !mIsInternal && isPrimary;
// Start the thread that will measure the disk usage.
final HandlerThread handlerThread = new HandlerThread("MemoryMeasurement");
@@ -126,6 +129,13 @@ public class StorageMeasurement {
*/
public static StorageMeasurement getInstance(Context context, StorageVolume storageVolume,
boolean isPrimary) {
+ if (storageVolume == null) {
+ if (sInternalInstance == null) {
+ sInternalInstance =
+ new StorageMeasurement(context.getApplicationContext(), storageVolume, isPrimary);
+ }
+ return sInternalInstance;
+ }
if (sInstances.containsKey(storageVolume)) {
return sInstances.get(storageVolume);
} else {
@@ -317,9 +327,18 @@ public class StorageMeasurement {
}
if (succeeded) {
- mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
- stats.externalCacheSize + stats.externalDataSize +
- stats.externalMediaSize + stats.externalObbSize;
+ if (mIsInternal) {
+ mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize;
+ } else if (!Environment.isExternalStorageEmulated()) {
+ mAppsSizeForThisStatsObserver += stats.externalObbSize +
+ stats.externalCodeSize + stats.externalDataSize +
+ stats.externalCacheSize + stats.externalMediaSize;
+ } else {
+ mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
+ stats.externalCodeSize + stats.externalDataSize +
+ stats.externalCacheSize + stats.externalMediaSize +
+ stats.externalObbSize;
+ }
}
synchronized (mAppsList) {
@@ -349,7 +368,8 @@ public class StorageMeasurement {
}
private void measureApproximateStorage() {
- final StatFs stat = new StatFs(mStorageVolume.getPath());
+ final StatFs stat = new StatFs(mStorageVolume != null
+ ? mStorageVolume.getPath() : Environment.getDataDirectory().getPath());
final long blockSize = stat.getBlockSize();
final long totalBlocks = stat.getBlockCount();
final long availableBlocks = stat.getAvailableBlocks();
@@ -434,7 +454,7 @@ public class StorageMeasurement {
return;
}
final List<ApplicationInfo> apps;
- if (mIsPrimary) {
+ if (mIsPrimary || mIsInternal) {
apps = pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES |
PackageManager.GET_DISABLED_COMPONENTS);
} else {
@@ -529,7 +549,7 @@ public class StorageMeasurement {
/**
* TODO remove this method, only used because external SD Card needs a special treatment.
*/
- boolean isPrimary() {
- return mIsPrimary;
+ boolean isExternalSDCard() {
+ return !mIsPrimary && !mIsInternal;
}
}
diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
index 9dbff88..4d22548 100644
--- a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
+++ b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
@@ -162,12 +162,13 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory implemen
mResources = resources;
mStorageVolume = storageVolume;
mStorageManager = storageManager;
- setTitle(storageVolume.getDescription());
+ setTitle(storageVolume != null ? storageVolume.getDescription()
+ : resources.getText(R.string.internal_storage));
mMeasurement = StorageMeasurement.getInstance(context, storageVolume, isPrimary);
mMeasurement.setReceiver(this);
// Cannot format emulated storage
- mAllowFormat = !mStorageVolume.isEmulated();
+ mAllowFormat = mStorageVolume != null && !mStorageVolume.isEmulated();
// For now we are disabling reformatting secondary external storage
// until some interoperability problems with MTP are fixed
if (!isPrimary) mAllowFormat = false;
@@ -240,7 +241,9 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory implemen
private void updatePreferencesFromState() {
resetPreferences();
- String state = mStorageManager.getVolumeState(mStorageVolume.getPath());
+ String state = mStorageVolume != null
+ ? mStorageManager.getVolumeState(mStorageVolume.getPath())
+ : Environment.MEDIA_MOUNTED;
String readOnly = "";
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
@@ -251,11 +254,8 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory implemen
}
}
- if (mFormatPreference != null) {
- removePreference(mFormatPreference);
- }
-
- if (!mStorageVolume.isRemovable() && !Environment.MEDIA_UNMOUNTED.equals(state)) {
+ if ((mStorageVolume == null || !mStorageVolume.isRemovable())
+ && !Environment.MEDIA_UNMOUNTED.equals(state)) {
// This device has built-in storage that is not removable.
// There is no reason for the user to unmount it.
removePreference(mMountTogglePreference);
@@ -307,7 +307,7 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory implemen
mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize));
- if (!mMeasurement.isPrimary()) {
+ if (mMeasurement.isExternalSDCard()) {
// TODO FIXME: external SD card will not report any size. Show used space in bar graph
final long usedSize = totalSize - availSize;
mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY);
diff --git a/src/com/android/settings/deviceinfo/UsageBarPreference.java b/src/com/android/settings/deviceinfo/UsageBarPreference.java
index e9909f1..5aeaef5 100644
--- a/src/com/android/settings/deviceinfo/UsageBarPreference.java
+++ b/src/com/android/settings/deviceinfo/UsageBarPreference.java
@@ -36,17 +36,17 @@ public class UsageBarPreference extends Preference {
public UsageBarPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- setWidgetLayoutResource(R.layout.preference_memoryusage);
+ setLayoutResource(R.layout.preference_memoryusage);
}
public UsageBarPreference(Context context) {
super(context);
- setWidgetLayoutResource(R.layout.preference_memoryusage);
+ setLayoutResource(R.layout.preference_memoryusage);
}
public UsageBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
- setWidgetLayoutResource(R.layout.preference_memoryusage);
+ setLayoutResource(R.layout.preference_memoryusage);
}
public void addEntry(float percentage, int color) {
diff --git a/src/com/android/settings/deviceinfo/UsbSettings.java b/src/com/android/settings/deviceinfo/UsbSettings.java
new file mode 100644
index 0000000..538cde7
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/UsbSettings.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.deviceinfo;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+/**
+ * USB storage settings.
+ */
+public class UsbSettings extends SettingsPreferenceFragment {
+
+ private static final String TAG = "UsbSettings";
+
+ private static final String KEY_MTP = "usb_mtp";
+ private static final String KEY_PTP = "usb_ptp";
+
+ private UsbManager mUsbManager;
+ private CheckBoxPreference mMtp;
+ private CheckBoxPreference mPtp;
+
+ private final BroadcastReceiver mStateReceiver = new BroadcastReceiver() {
+ public void onReceive(Context content, Intent intent) {
+ updateToggles();
+ }
+ };
+
+ private PreferenceScreen createPreferenceHierarchy() {
+ PreferenceScreen root = getPreferenceScreen();
+ if (root != null) {
+ root.removeAll();
+ }
+ addPreferencesFromResource(R.xml.usb_settings);
+ root = getPreferenceScreen();
+
+ mMtp = (CheckBoxPreference)root.findPreference(KEY_MTP);
+ mPtp = (CheckBoxPreference)root.findPreference(KEY_PTP);
+
+ return root;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getActivity().unregisterReceiver(mStateReceiver);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Make sure we reload the preference hierarchy since some of these settings
+ // depend on others...
+ createPreferenceHierarchy();
+
+ // ACTION_USB_STATE is sticky so this will call updateToggles
+ getActivity().registerReceiver(mStateReceiver,
+ new IntentFilter(UsbManager.ACTION_USB_STATE));
+ }
+
+ private void updateToggles() {
+ String function = mUsbManager.getDefaultFunction();
+ if (UsbManager.USB_FUNCTION_MTP.equals(function)) {
+ mMtp.setChecked(true);
+ mPtp.setChecked(false);
+ } else if (UsbManager.USB_FUNCTION_PTP.equals(function)) {
+ mMtp.setChecked(false);
+ mPtp.setChecked(true);
+ } else {
+ mMtp.setChecked(false);
+ mPtp.setChecked(false);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+
+ // temporary hack - using check boxes as radio buttons
+ // don't allow unchecking them
+ if (preference instanceof CheckBoxPreference) {
+ CheckBoxPreference checkBox = (CheckBoxPreference)preference;
+ if (!checkBox.isChecked()) {
+ checkBox.setChecked(true);
+ return true;
+ }
+ }
+ if (preference == mMtp) {
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);
+ } else if (preference == mPtp) {
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true);
+ }
+ updateToggles();
+ return true;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
index 97ebf43..13a962d 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
@@ -368,9 +368,9 @@ public class BatteryHistoryChart extends View {
}
if (rec.batteryLevel != lastLevel || pos == 1) {
lastLevel = rec.batteryLevel;
- lastInteresting = pos;
- mHistEnd = rec.time;
}
+ lastInteresting = pos;
+ mHistEnd = rec.time;
aggrStates |= rec.states;
}
}
@@ -438,7 +438,13 @@ public class BatteryHistoryChart extends View {
2, getResources().getDisplayMetrics());
if (h > (textHeight*6)) {
mLargeMode = true;
- mLineWidth = textHeight/2;
+ if (h > (textHeight*15)) {
+ // Plenty of room for the chart.
+ mLineWidth = textHeight/2;
+ } else {
+ // Compress lines to make more room for chart.
+ mLineWidth = textHeight/3;
+ }
mLevelTop = textHeight + mLineWidth;
mScreenOnPaint.setARGB(255, 32, 64, 255);
mGpsOnPaint.setARGB(255, 32, 64, 255);
@@ -472,7 +478,8 @@ public class BatteryHistoryChart extends View {
mWifiRunningOffset = mWakeLockOffset + barOffset;
mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? barOffset : 0);
mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? barOffset : 0);
- mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0) + mLineWidth;
+ mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0)
+ + ((mLineWidth*3)/2);
if (mHavePhoneSignal) {
mPhoneSignalChart.init(w);
}
@@ -670,8 +677,8 @@ public class BatteryHistoryChart extends View {
if (!mBatCriticalPath.isEmpty()) {
canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
}
- int top = height - (mHavePhoneSignal ? mPhoneSignalOffset - (mLineWidth/2) : 0);
if (mHavePhoneSignal) {
+ int top = height-mPhoneSignalOffset - (mLineWidth/2);
mPhoneSignalChart.draw(canvas, top, mLineWidth);
}
if (!mScreenOnPath.isEmpty()) {
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index fc903eb..f28ba93 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -16,18 +16,14 @@
package com.android.settings.fuelgauge;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.os.PowerProfile;
-import com.android.settings.R;
-import com.android.settings.applications.InstalledAppDetails;
-import com.android.settings.fuelgauge.PowerUsageDetail.DrainType;
-
-import android.content.ContentResolver;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.SensorManager;
import android.os.BatteryStats;
+import android.os.BatteryStats.Uid;
+import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -36,7 +32,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.BatteryStats.Uid;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
@@ -49,6 +44,12 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatteryStatsImpl;
+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;
@@ -67,6 +68,9 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
private static final String TAG = "PowerUsageSummary";
+ private static final String KEY_APP_LIST = "app_list";
+ private static final String KEY_BATTERY_STATUS = "battery_status";
+
private static final int MENU_STATS_TYPE = Menu.FIRST;
private static final int MENU_STATS_REFRESH = Menu.FIRST + 1;
@@ -79,6 +83,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
private PreferenceGroup mAppListGroup;
+ private Preference mBatteryStatusPref;
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
@@ -99,7 +104,23 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
private ArrayList<BatterySipper> mRequestQueue = new ArrayList<BatterySipper>();
private Thread mRequestThread;
private boolean mAbort;
-
+
+ private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ String batteryLevel = com.android.settings.Utils.getBatteryPercentage(intent);
+ String batteryStatus = com.android.settings.Utils.getBatteryStatus(getResources(),
+ intent);
+ String batterySummary = context.getResources().getString(
+ R.string.power_usage_level_and_status, batteryLevel, batteryStatus);
+ mBatteryStatusPref.setTitle(batterySummary);
+ }
+ }
+ };
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -111,7 +132,8 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
addPreferencesFromResource(R.xml.power_usage_summary);
mBatteryInfo = IBatteryStats.Stub.asInterface(
ServiceManager.getService("batteryinfo"));
- mAppListGroup = (PreferenceGroup) findPreference("app_list");
+ mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
+ mBatteryStatusPref = mAppListGroup.findPreference(KEY_BATTERY_STATUS);
mPowerProfile = new PowerProfile(getActivity());
setHasOptionsMenu(true);
}
@@ -120,6 +142,8 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
public void onResume() {
super.onResume();
mAbort = false;
+ getActivity().registerReceiver(mBatteryInfoReceiver,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
refreshStats();
}
@@ -129,6 +153,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
mAbort = true;
}
mHandler.removeMessages(MSG_UPDATE_NAME_ICON);
+ getActivity().unregisterReceiver(mBatteryInfoReceiver);
super.onPause();
}
@@ -292,7 +317,8 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh)
.setIcon(R.drawable.ic_menu_refresh_holo_dark)
.setAlphabeticShortcut('r');
- refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
+ MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
@Override
@@ -337,6 +363,8 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable {
mBluetoothSippers.clear();
mAppListGroup.setOrderingAsAdded(false);
+ mBatteryStatusPref.setOrder(-2);
+ mAppListGroup.addPreference(mBatteryStatusPref);
BatteryHistoryPreference hist = new BatteryHistoryPreference(getActivity(), mStats);
hist.setOrder(-1);
mAppListGroup.addPreference(hist);
diff --git a/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java b/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java
new file mode 100644
index 0000000..f983f59
--- /dev/null
+++ b/src/com/android/settings/inputmethod/CheckBoxAndSettingsPreference.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import android.content.Context;
+import android.content.Intent;
+import android.preference.CheckBoxPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class CheckBoxAndSettingsPreference extends CheckBoxPreference {
+ private static final float DISABLED_ALPHA = 0.4f;
+
+ private SettingsPreferenceFragment mFragment;
+ private TextView mTitleText;
+ private TextView mSummaryText;
+ private View mCheckBox;
+ private ImageView mSetingsButton;
+ private Intent mSettingsIntent;
+
+ public CheckBoxAndSettingsPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setLayoutResource(R.layout.preference_inputmethod);
+ setWidgetLayoutResource(R.layout.preference_inputmethod_widget);
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ mCheckBox = view.findViewById(R.id.inputmethod_pref);
+ mCheckBox.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ onCheckBoxClicked(arg0);
+ }
+ });
+ mSetingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings);
+ mTitleText = (TextView)view.findViewById(android.R.id.title);
+ mSummaryText = (TextView)view.findViewById(android.R.id.summary);
+ mSetingsButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ onSettingsButtonClicked(arg0);
+ }
+ });
+ enableSettingsButton();
+ }
+
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ enableSettingsButton();
+ }
+
+ public void setFragmentIntent(SettingsPreferenceFragment fragment, Intent intent) {
+ mFragment = fragment;
+ mSettingsIntent = intent;
+ }
+
+ protected void onCheckBoxClicked(View view) {
+ if (isChecked()) {
+ setChecked(false);
+ } else {
+ setChecked(true);
+ }
+ }
+
+ protected void onSettingsButtonClicked(View arg0) {
+ if (mFragment != null && mSettingsIntent != null) {
+ mFragment.startActivity(mSettingsIntent);
+ }
+ }
+
+ private void enableSettingsButton() {
+ if (mSetingsButton != null) {
+ if (mSettingsIntent == null) {
+ mSetingsButton.setVisibility(View.GONE);
+ } else {
+ final boolean checked = isChecked();
+ mSetingsButton.setEnabled(checked);
+ mSetingsButton.setClickable(checked);
+ mSetingsButton.setFocusable(checked);
+ if (!checked) {
+ mSetingsButton.setAlpha(DISABLED_ALPHA);
+ }
+ }
+ }
+ if (mTitleText != null) {
+ mTitleText.setEnabled(true);
+ }
+ if (mSummaryText != null) {
+ mSummaryText.setEnabled(true);
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index a4808b0..699a4a6 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -17,29 +17,63 @@
package com.android.settings.inputmethod;
import com.android.settings.R;
+import com.android.settings.Settings.SpellCheckersSettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.UserDictionarySettings;
import com.android.settings.Utils;
import com.android.settings.VoiceInputOutputSettings;
+import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.provider.Settings;
+import android.provider.Settings.System;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
implements Preference.OnPreferenceChangeListener{
private static final String KEY_PHONE_LANGUAGE = "phone_language";
private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
+ private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
+ // false: on ICS or later
+ private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
+
+ private static final String[] sSystemSettingNames = {
+ System.TEXT_AUTO_REPLACE, System.TEXT_AUTO_CAPS, System.TEXT_AUTO_PUNCTUATE,
+ };
+
+ private static final String[] sHardKeyboardKeys = {
+ "auto_replace", "auto_caps", "auto_punctuate",
+ };
private int mDefaultInputMethodSelectorVisibility = 0;
private ListPreference mShowInputMethodSelectorPref;
private Preference mLanguagePref;
+ private ArrayList<InputMethodPreference> mInputMethodPreferenceList =
+ new ArrayList<InputMethodPreference>();
+ private boolean mHaveHardKeyboard;
+ private PreferenceCategory mHardKeyboardCategory;
+ private InputMethodManager mImm;
+ private List<InputMethodInfo> mImis;
+ private boolean mIsOnlyImeSettings;
@Override
public void onCreate(Bundle icicle) {
@@ -59,13 +93,34 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
} else {
mLanguagePref = findPreference(KEY_PHONE_LANGUAGE);
}
- mShowInputMethodSelectorPref = (ListPreference)findPreference(
- KEY_INPUT_METHOD_SELECTOR);
- mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
- // TODO: Update current input method name on summary
- updateInputMethodSelectorSummary(loadInputMethodSelectorVisibility());
+ if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
+ mShowInputMethodSelectorPref = (ListPreference)findPreference(
+ KEY_INPUT_METHOD_SELECTOR);
+ mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
+ // TODO: Update current input method name on summary
+ updateInputMethodSelectorSummary(loadInputMethodSelectorVisibility());
+ }
new VoiceInputOutputSettings(this).onCreate();
+
+ // Hard keyboard
+ final Configuration config = getResources().getConfiguration();
+ mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
+
+ // IME
+ mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
+ getActivity().getIntent().getAction());
+ mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ mImis = mImm.getInputMethodList();
+ createImePreferenceHierarchy((PreferenceGroup)findPreference("keyboard_settings_category"));
+
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(getActivity(), SpellCheckersSettingsActivity.class);
+ final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference(
+ "spellcheckers_settings"));
+ if (scp != null) {
+ scp.setFragmentIntent(this, intent);
+ }
}
private void updateInputMethodSelectorSummary(int value) {
@@ -77,25 +132,76 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
}
}
+ private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
+ final Activity activity = getActivity();
+ final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity);
+ if (null == localeList) {
+ // The locale list is null if and only if the user dictionary service is
+ // not present or disabled. In this case we need to remove the preference.
+ ((PreferenceGroup)findPreference("language_settings_category")).removePreference(
+ userDictionaryPreference);
+ } else if (localeList.size() <= 1) {
+ userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title);
+ userDictionaryPreference.setFragment(UserDictionarySettings.class.getName());
+ // If the size of localeList is 0, we don't set the locale parameter in the
+ // extras. This will be interpreted by the UserDictionarySettings class as
+ // meaning "the current locale".
+ // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesList()
+ // the locale list always has at least one element, since it always includes the current
+ // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesList().
+ if (localeList.size() == 1) {
+ final String locale = (String)localeList.toArray()[0];
+ userDictionaryPreference.getExtras().putString("locale", locale);
+ }
+ } else {
+ userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title);
+ userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
- 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);
+ if (!mIsOnlyImeSettings) {
+ 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);
+ }
+ }
+
+ updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
+ if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
+ mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
+ }
+ }
+
+ // Hard keyboard
+ if (mHaveHardKeyboard) {
+ for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
+ InputMethodPreference chkPref = (InputMethodPreference)
+ mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]);
+ chkPref.setChecked(
+ System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0);
}
}
- mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
+ // IME
+ InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
+ this, getContentResolver(), mImis, null);
+ updateActiveInputMethodsSummary();
}
@Override
public void onPause() {
super.onPause();
- mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null);
+ if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
+ mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null);
+ }
+ InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
+ this, getContentResolver(), mImis, mHaveHardKeyboard);
}
@Override
@@ -112,6 +218,17 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showInputMethodPicker();
}
+ } else if (preference instanceof CheckBoxPreference) {
+ final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
+ if (mHaveHardKeyboard) {
+ for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
+ if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
+ System.putInt(getContentResolver(), sSystemSettingNames[i],
+ chkPref.isChecked() ? 1 : 0);
+ return true;
+ }
+ }
+ }
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
}
@@ -134,12 +251,84 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
- if (preference == mShowInputMethodSelectorPref) {
- if (value instanceof String) {
- saveInputMethodSelectorVisibility((String)value);
+ if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
+ if (preference == mShowInputMethodSelectorPref) {
+ if (value instanceof String) {
+ saveInputMethodSelectorVisibility((String)value);
+ }
}
}
return false;
}
+ private void updateActiveInputMethodsSummary() {
+ for (Preference pref : mInputMethodPreferenceList) {
+ if (pref instanceof InputMethodPreference) {
+ ((InputMethodPreference)pref).updateSummary();
+ }
+ }
+ }
+
+ private InputMethodPreference getInputMethodPreference(InputMethodInfo imi, int imiSize) {
+ final PackageManager pm = getPackageManager();
+ final CharSequence label = imi.loadLabel(pm);
+ // IME settings
+ final Intent intent;
+ final String settingsActivity = imi.getSettingsActivity();
+ if (!TextUtils.isEmpty(settingsActivity)) {
+ intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(imi.getPackageName(), settingsActivity);
+ } else {
+ intent = null;
+ }
+
+ // Add a check box for enabling/disabling IME
+ InputMethodPreference pref = new InputMethodPreference(this, intent, mImm, imi, imiSize);
+ pref.setKey(imi.getId());
+ pref.setTitle(label);
+ return pref;
+ }
+
+ private void createImePreferenceHierarchy(PreferenceGroup root) {
+ final Preference hardKeyPref = findPreference("hard_keyboard");
+ if (mIsOnlyImeSettings) {
+ getPreferenceScreen().removeAll();
+ if (hardKeyPref != null && mHaveHardKeyboard) {
+ getPreferenceScreen().addPreference(hardKeyPref);
+ }
+ if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
+ getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
+ }
+ getPreferenceScreen().addPreference(root);
+ }
+ if (hardKeyPref != null) {
+ if (mHaveHardKeyboard) {
+ mHardKeyboardCategory = (PreferenceCategory) hardKeyPref;
+ } else {
+ getPreferenceScreen().removePreference(hardKeyPref);
+ }
+ }
+ root.removeAll();
+ mInputMethodPreferenceList.clear();
+
+ if (!mIsOnlyImeSettings) {
+ // Current IME selection
+ final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null);
+ currentIme.setKey(KEY_CURRENT_INPUT_METHOD);
+ currentIme.setTitle(getResources().getString(R.string.current_input_method));
+ root.addPreference(currentIme);
+ }
+
+ final int N = (mImis == null ? 0 : mImis.size());
+ for (int i = 0; i < N; ++i) {
+ final InputMethodInfo imi = mImis.get(i);
+ final InputMethodPreference pref = getInputMethodPreference(imi, N);
+ mInputMethodPreferenceList.add(pref);
+ }
+
+ Collections.sort(mInputMethodPreferenceList);
+ for (int i = 0; i < N; ++i) {
+ root.addPreference(mInputMethodPreferenceList.get(i));
+ }
+ }
}
diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
index 6e1d4d1..e5ce987 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
@@ -22,6 +22,7 @@ import com.android.settings.SettingsPreferenceFragment;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -48,6 +49,7 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
private InputMethodManager mImm;
private List<InputMethodInfo> mInputMethodProperties;
private String mInputMethodId;
+ private String mTitle;
@Override
public void onCreate(Bundle icicle) {
@@ -56,25 +58,41 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
Configuration config = getResources().getConfiguration();
mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
+ final Bundle arguments = getArguments();
// Input method id should be available from an Intent when this preference is launched as a
// single Activity (see InputMethodAndSubtypeEnablerActivity). It should be available
// from a preference argument when the preference is launched as a part of the other
// Activity (like a right pane of 2-pane Settings app)
mInputMethodId = getActivity().getIntent().getStringExtra(
android.provider.Settings.EXTRA_INPUT_METHOD_ID);
- if (mInputMethodId == null && (getArguments() != null)) {
+ if (mInputMethodId == null && (arguments != null)) {
final String inputMethodId =
- getArguments().getString(android.provider.Settings.EXTRA_INPUT_METHOD_ID);
+ arguments.getString(android.provider.Settings.EXTRA_INPUT_METHOD_ID);
if (inputMethodId != null) {
mInputMethodId = inputMethodId;
}
}
+ mTitle = getActivity().getIntent().getStringExtra(Intent.EXTRA_TITLE);
+ if (mTitle == null && (arguments != null)) {
+ final String title = arguments.getString(Intent.EXTRA_TITLE);
+ if (title != null) {
+ mTitle = title;
+ }
+ }
onCreateIMM();
setPreferenceScreen(createPreferenceHierarchy());
}
@Override
+ public void onActivityCreated(Bundle icicle) {
+ super.onActivityCreated(icicle);
+ if (!TextUtils.isEmpty(mTitle)) {
+ getActivity().setTitle(mTitle);
+ }
+ }
+
+ @Override
public void onResume() {
super.onResume();
InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
@@ -136,6 +154,7 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
.setCancelable(true)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int which) {
chkPref.setChecked(true);
InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled(
@@ -146,6 +165,7 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int which) {
}
@@ -188,7 +208,8 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
private PreferenceScreen createPreferenceHierarchy() {
// Root
- PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
+ final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
+ final Context context = getActivity();
int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size());
@@ -202,7 +223,7 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
if (!TextUtils.isEmpty(mInputMethodId) && !mInputMethodId.equals(imiId)) {
continue;
}
- PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(getActivity());
+ PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context);
root.addPreference(keyboardSettingsCategory);
PackageManager pm = getPackageManager();
CharSequence label = imi.loadLabel(pm);
@@ -210,31 +231,22 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
keyboardSettingsCategory.setTitle(label);
keyboardSettingsCategory.setKey(imiId);
// TODO: Use toggle Preference if images are ready.
- CheckBoxPreference autoCB = new CheckBoxPreference(getActivity());
+ CheckBoxPreference autoCB = new CheckBoxPreference(context);
autoCB.setTitle(R.string.use_system_language_to_select_input_method_subtypes);
mSubtypeAutoSelectionCBMap.put(imiId, autoCB);
keyboardSettingsCategory.addPreference(autoCB);
- PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(getActivity());
+ PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context);
activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
root.addPreference(activeInputMethodsCategory);
ArrayList<Preference> subtypePreferences = new ArrayList<Preference>();
if (subtypeCount > 0) {
for (int j = 0; j < subtypeCount; ++j) {
- InputMethodSubtype subtype = imi.getSubtypeAt(j);
- CharSequence subtypeLabel;
- int nameResId = subtype.getNameResId();
- if (nameResId != 0) {
- subtypeLabel = pm.getText(imi.getPackageName(), nameResId,
- imi.getServiceInfo().applicationInfo);
- } else {
- String mode = subtype.getMode();
- CharSequence language = subtype.getLocale();
- subtypeLabel = (mode == null ? "" : mode) + ","
- + (language == null ? "" : language);
- }
- CheckBoxPreference chkbxPref = new CheckBoxPreference(getActivity());
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ final CharSequence subtypeLabel = subtype.getDisplayName(context,
+ imi.getPackageName(), imi.getServiceInfo().applicationInfo);
+ final CheckBoxPreference chkbxPref = new CheckBoxPreference(context);
chkbxPref.setKey(imiId + subtype.hashCode());
chkbxPref.setTitle(subtypeLabel);
activeInputMethodsCategory.addPreference(chkbxPref);
diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
index 362fbb5..f62edc4 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
@@ -169,9 +169,10 @@ public class InputMethodAndSubtypeUtil {
final boolean isImeChecked = (pref instanceof CheckBoxPreference) ?
((CheckBoxPreference) pref).isChecked()
: enabledIMEAndSubtypesMap.containsKey(imiId);
- boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
- boolean systemIme = isSystemIme(imi);
- if (((onlyOneIME || systemIme) && !hasHardKeyboard) || isImeChecked) {
+ final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
+ final boolean auxIme = isAuxiliaryIme(imi);
+ final boolean systemIme = isSystemIme(imi);
+ if (((onlyOneIME || (systemIme && !auxIme)) && !hasHardKeyboard) || isImeChecked) {
if (!enabledIMEAndSubtypesMap.containsKey(imiId)) {
// imiId has just been enabled
enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>());
@@ -276,7 +277,7 @@ public class InputMethodAndSubtypeUtil {
List<InputMethodInfo> inputMethodInfos,
final Map<String, List<Preference>> inputMethodPrefsMap) {
HashMap<String, HashSet<String>> enabledSubtypes =
- getEnabledInputMethodsAndSubtypeList(resolver);
+ getEnabledInputMethodsAndSubtypeList(resolver);
for (InputMethodInfo imi : inputMethodInfos) {
final String imiId = imi.getId();
@@ -342,4 +343,19 @@ public class InputMethodAndSubtypeUtil {
return (property.getServiceInfo().applicationInfo.flags
& ApplicationInfo.FLAG_SYSTEM) != 0;
}
+
+ public static boolean isAuxiliaryIme(InputMethodInfo imi) {
+ final int subtypeCount = imi.getSubtypeCount();
+ if (subtypeCount == 0) {
+ return false;
+ } else {
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (!subtype.isAuxiliary()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
}
diff --git a/src/com/android/settings/inputmethod/InputMethodConfig.java b/src/com/android/settings/inputmethod/InputMethodConfig.java
deleted file mode 100644
index 2cfe35d..0000000
--- a/src/com/android/settings/inputmethod/InputMethodConfig.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * 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.inputmethod;
-
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-import android.app.AlertDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.provider.Settings.System;
-import android.text.TextUtils;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class InputMethodConfig extends SettingsPreferenceFragment {
-
- private static final String[] sSystemSettingNames = {
- System.TEXT_AUTO_REPLACE, System.TEXT_AUTO_CAPS, System.TEXT_AUTO_PUNCTUATE,
- };
-
- private static final String[] sHardKeyboardKeys = {
- "auto_replace", "auto_caps", "auto_punctuate",
- };
-
- private AlertDialog mDialog = null;
- private boolean mHaveHardKeyboard;
- private PreferenceCategory mHardKeyboardCategory;
- // Map of imi and its preferences
- final private HashMap<String, List<Preference>> mInputMethodPrefsMap =
- new HashMap<String, List<Preference>>();
- final private HashMap<InputMethodInfo, Preference> mActiveInputMethodsPrefMap =
- new HashMap<InputMethodInfo, Preference>();
- private List<InputMethodInfo> mInputMethodProperties;
-
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- Configuration config = getResources().getConfiguration();
- mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
- InputMethodManager imm = (InputMethodManager) getSystemService(
- Context.INPUT_METHOD_SERVICE);
-
- // TODO: Change mInputMethodProperties to Map
- mInputMethodProperties = imm.getInputMethodList();
- setPreferenceScreen(createPreferenceHierarchy());
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- ContentResolver resolver = getContentResolver();
- if (mHaveHardKeyboard) {
- for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
- CheckBoxPreference chkPref = (CheckBoxPreference)
- mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]);
- chkPref.setChecked(System.getInt(resolver, sSystemSettingNames[i], 1) > 0);
- }
- }
-
- InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
- this, resolver, mInputMethodProperties, mInputMethodPrefsMap);
- updateActiveInputMethodsSummary();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
- mInputMethodProperties, mHaveHardKeyboard);
- }
-
- private void showSecurityWarnDialog(InputMethodInfo imi, final CheckBoxPreference chkPref,
- final String imiId) {
- if (mDialog != null && mDialog.isShowing()) {
- mDialog.dismiss();
- }
- mDialog = (new AlertDialog.Builder(getActivity()))
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setCancelable(true)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- chkPref.setChecked(true);
- for (Preference pref: mInputMethodPrefsMap.get(imiId)) {
- pref.setEnabled(true);
- }
- }
- })
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- }
- })
- .create();
- mDialog.setMessage(getResources().getString(R.string.ime_security_warning,
- imi.getServiceInfo().applicationInfo.loadLabel(getPackageManager())));
- mDialog.show();
- }
-
- private InputMethodInfo getInputMethodInfoFromImiId(String imiId) {
- final int N = mInputMethodProperties.size();
- for (int i = 0; i < N; ++i) {
- InputMethodInfo imi = mInputMethodProperties.get(i);
- if (imiId.equals(imi.getId())) {
- return imi;
- }
- }
- return null;
- }
-
- @Override
- public boolean onPreferenceTreeClick(
- PreferenceScreen preferenceScreen, Preference preference) {
-
- if (preference instanceof CheckBoxPreference) {
- final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
-
- if (mHaveHardKeyboard) {
- for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
- if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
- System.putInt(getContentResolver(), sSystemSettingNames[i],
- chkPref.isChecked() ? 1 : 0);
- return true;
- }
- }
- }
-
- final String imiId = chkPref.getKey();
- if (chkPref.isChecked()) {
- InputMethodInfo selImi = getInputMethodInfoFromImiId(imiId);
- if (selImi != null) {
- if (InputMethodAndSubtypeUtil.isSystemIme(selImi)) {
- // This is a built-in IME, so no need to warn.
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
- } else {
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
- chkPref.setChecked(false);
- showSecurityWarnDialog(selImi, chkPref, imiId);
- } else {
- for (Preference pref: mInputMethodPrefsMap.get(imiId)) {
- pref.setEnabled(false);
- }
- }
- }
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
- }
-
- private void addInputMethodPreference(PreferenceScreen root, InputMethodInfo imi,
- final int imiSize) {
- PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(getActivity());
- root.addPreference(keyboardSettingsCategory);
- final String imiId = imi.getId();
- mInputMethodPrefsMap.put(imiId, new ArrayList<Preference>());
-
- PackageManager pm = getPackageManager();
- CharSequence label = imi.loadLabel(pm);
- keyboardSettingsCategory.setTitle(label);
-
- final boolean isSystemIME = InputMethodAndSubtypeUtil.isSystemIme(imi);
- // Add a check box for enabling/disabling IME
- CheckBoxPreference chkbxPref = new CheckBoxPreference(getActivity());
- chkbxPref.setKey(imiId);
- chkbxPref.setTitle(label);
- keyboardSettingsCategory.addPreference(chkbxPref);
- // Disable the toggle if it's the only keyboard in the system, or it's a system IME.
- if (imiSize <= 1 || isSystemIME) {
- chkbxPref.setEnabled(false);
- }
-
- Intent intent;
- // Add subtype settings when this IME has two or more subtypes.
- PreferenceScreen prefScreen = new PreferenceScreen(getActivity(), null);
- prefScreen.setTitle(R.string.active_input_method_subtypes);
- if (imi.getSubtypeCount() > 1) {
- prefScreen.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference){
- final Bundle bundle = new Bundle();
- bundle.putString(Settings.EXTRA_INPUT_METHOD_ID, imiId);
- startFragment(InputMethodConfig.this,
- InputMethodAndSubtypeEnabler.class.getName(),
- 0, bundle);
- return true;
- }
- });
- keyboardSettingsCategory.addPreference(prefScreen);
- mActiveInputMethodsPrefMap.put(imi, prefScreen);
- mInputMethodPrefsMap.get(imiId).add(prefScreen);
- }
-
- // Add IME settings
- String settingsActivity = imi.getSettingsActivity();
- if (!TextUtils.isEmpty(settingsActivity)) {
- prefScreen = new PreferenceScreen(getActivity(), null);
- prefScreen.setTitle(R.string.input_method_settings);
- intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName(imi.getPackageName(), settingsActivity);
- prefScreen.setIntent(intent);
- keyboardSettingsCategory.addPreference(prefScreen);
- mInputMethodPrefsMap.get(imiId).add(prefScreen);
- }
- }
-
- private PreferenceScreen createPreferenceHierarchy() {
- addPreferencesFromResource(R.xml.hard_keyboard_settings);
- PreferenceScreen root = getPreferenceScreen();
-
- if (mHaveHardKeyboard) {
- mHardKeyboardCategory = (PreferenceCategory) findPreference("hard_keyboard");
- } else {
- root.removeAll();
- }
-
- final int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size());
- for (int i = 0; i < N; ++i) {
- addInputMethodPreference(root, mInputMethodProperties.get(i), N);
- }
- return root;
- }
-
- private void updateActiveInputMethodsSummary() {
- final InputMethodManager imm =
- (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- final PackageManager pm = getPackageManager();
- for (InputMethodInfo imi: mActiveInputMethodsPrefMap.keySet()) {
- Preference pref = mActiveInputMethodsPrefMap.get(imi);
- List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(imi, true);
- StringBuilder summary = new StringBuilder();
- boolean subtypeAdded = false;
- for (InputMethodSubtype subtype: subtypes) {
- if (subtypeAdded) {
- summary.append(", ");
- }
- summary.append(pm.getText(imi.getPackageName(), subtype.getNameResId(),
- imi.getServiceInfo().applicationInfo));
- subtypeAdded = true;
- }
- pref.setSummary(summary.toString());
- }
- }
-}
diff --git a/src/com/android/settings/inputmethod/InputMethodDialogReceiver.java b/src/com/android/settings/inputmethod/InputMethodDialogReceiver.java
new file mode 100644
index 0000000..46be132
--- /dev/null
+++ b/src/com/android/settings/inputmethod/InputMethodDialogReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.inputmethod;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.view.inputmethod.InputMethodManager;
+
+public class InputMethodDialogReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Settings.ACTION_SHOW_INPUT_METHOD_PICKER.equals(intent.getAction())) {
+ ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE))
+ .showInputMethodPicker();
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/InputMethodPreference.java b/src/com/android/settings/inputmethod/InputMethodPreference.java
new file mode 100644
index 0000000..f490fd2
--- /dev/null
+++ b/src/com/android/settings/inputmethod/InputMethodPreference.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.PreferenceActivity;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.Comparator;
+import java.util.List;
+
+public class InputMethodPreference extends CheckBoxPreference
+ implements Comparator<InputMethodPreference> {
+ private static final String TAG = InputMethodPreference.class.getSimpleName();
+ private static final float DISABLED_ALPHA = 0.4f;
+ private final SettingsPreferenceFragment mFragment;
+ private final InputMethodInfo mImi;
+ private final InputMethodManager mImm;
+ private final Intent mSettingsIntent;
+ private final boolean mIsSystemIme;
+
+ private AlertDialog mDialog = null;
+ private ImageView mInputMethodSettingsButton;
+ private TextView mTitleText;
+ private TextView mSummaryText;
+ private View mInputMethodPref;
+
+ public InputMethodPreference(SettingsPreferenceFragment fragment, Intent settingsIntent,
+ InputMethodManager imm, InputMethodInfo imi, int imiCount) {
+ super(fragment.getActivity(), null, R.style.InputMethodPreferenceStyle);
+ setLayoutResource(R.layout.preference_inputmethod);
+ setWidgetLayoutResource(R.layout.preference_inputmethod_widget);
+ mFragment = fragment;
+ mSettingsIntent = settingsIntent;
+ mImm = imm;
+ mImi = imi;
+ updateSummary();
+ mIsSystemIme = InputMethodAndSubtypeUtil.isSystemIme(imi);
+ final boolean isAuxIme = InputMethodAndSubtypeUtil.isAuxiliaryIme(imi);
+ if (imiCount <= 1 || (mIsSystemIme && !isAuxIme)) {
+ setEnabled(false);
+ }
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ mInputMethodPref = view.findViewById(R.id.inputmethod_pref);
+ mInputMethodPref.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (isChecked()) {
+ setChecked(false);
+ } else {
+ if (mIsSystemIme) {
+ setChecked(true);
+ } else {
+ showSecurityWarnDialog(mImi, InputMethodPreference.this);
+ }
+ }
+ }
+ });
+ mInputMethodSettingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings);
+ mTitleText = (TextView)view.findViewById(android.R.id.title);
+ mSummaryText = (TextView)view.findViewById(android.R.id.summary);
+ if (mSettingsIntent != null) {
+ mInputMethodSettingsButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ try {
+ mFragment.startActivity(mSettingsIntent);
+ } catch (ActivityNotFoundException e) {
+ Log.d(TAG, "IME's Settings Activity Not Found: " + e);
+ // If the IME's settings activity does not exist, we can just
+ // do nothing...
+ }
+ }
+ });
+ }
+ final boolean hasSubtypes = mImi.getSubtypeCount() > 1;
+ final String imiId = mImi.getId();
+ if (hasSubtypes) {
+ final OnLongClickListener listener = new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View arg0) {
+ final Bundle bundle = new Bundle();
+ bundle.putString(Settings.EXTRA_INPUT_METHOD_ID, imiId);
+ startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
+ 0, bundle);
+ return true;
+ }
+ };
+ mInputMethodSettingsButton.setOnLongClickListener(listener);
+ }
+ if (mSettingsIntent == null) {
+ mInputMethodSettingsButton.setVisibility(View.GONE);
+ } else {
+ enableSettingsButton();
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ enableSettingsButton();
+ }
+
+ private void enableSettingsButton() {
+ if (mInputMethodSettingsButton != null) {
+ final boolean checked = isChecked();
+ mInputMethodSettingsButton.setEnabled(checked);
+ mInputMethodSettingsButton.setClickable(checked);
+ mInputMethodSettingsButton.setFocusable(checked);
+ if (!checked) {
+ mInputMethodSettingsButton.setAlpha(DISABLED_ALPHA);
+ }
+ }
+ if (mTitleText != null) {
+ mTitleText.setEnabled(true);
+ }
+ if (mSummaryText != null) {
+ mSummaryText.setEnabled(true);
+ }
+ }
+
+ public static boolean startFragment(
+ Fragment fragment, String fragmentClass, int requestCode, Bundle extras) {
+ if (fragment.getActivity() instanceof PreferenceActivity) {
+ PreferenceActivity preferenceActivity = (PreferenceActivity)fragment.getActivity();
+ preferenceActivity.startPreferencePanel(fragmentClass, extras, 0, null, fragment,
+ requestCode);
+ return true;
+ } else {
+ Log.w(TAG, "Parent isn't PreferenceActivity, thus there's no way to launch the "
+ + "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode
+ + ")");
+ return false;
+ }
+ }
+
+ public String getSummaryString() {
+ final StringBuilder builder = new StringBuilder();
+ final List<InputMethodSubtype> subtypes = mImm.getEnabledInputMethodSubtypeList(mImi, true);
+ for (InputMethodSubtype subtype : subtypes) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ final CharSequence subtypeLabel = subtype.getDisplayName(mFragment.getActivity(),
+ mImi.getPackageName(), mImi.getServiceInfo().applicationInfo);
+ builder.append(subtypeLabel);
+ }
+ return builder.toString();
+ }
+
+ public void updateSummary() {
+ final String summary = getSummaryString();
+ if (TextUtils.isEmpty(summary)) {
+ return;
+ }
+ setSummary(summary);
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ super.setChecked(checked);
+ saveImeSettings();
+ }
+
+ private void showSecurityWarnDialog(InputMethodInfo imi, final CheckBoxPreference chkPref) {
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ mDialog = (new AlertDialog.Builder(mFragment.getActivity()))
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ chkPref.setChecked(true);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .create();
+ mDialog.setMessage(mFragment.getResources().getString(R.string.ime_security_warning,
+ imi.getServiceInfo().applicationInfo.loadLabel(
+ mFragment.getActivity().getPackageManager())));
+ mDialog.show();
+ }
+
+ @Override
+ public int compare(InputMethodPreference arg0, InputMethodPreference arg1) {
+ if (arg0.isEnabled() == arg0.isEnabled()) {
+ return arg0.mImi.getId().compareTo(arg1.mImi.getId());
+ } else {
+ // Prefer system IMEs
+ return arg0.isEnabled() ? 1 : -1;
+ }
+ }
+
+ private void saveImeSettings() {
+ InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
+ mFragment, mFragment.getActivity().getContentResolver(), mImm.getInputMethodList(),
+ mFragment.getResources().getConfiguration().keyboard
+ == Configuration.KEYBOARD_QWERTY);
+ }
+}
diff --git a/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java b/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java
new file mode 100644
index 0000000..98ca3af
--- /dev/null
+++ b/src/com/android/settings/inputmethod/SingleSpellCheckerPreference.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import android.content.Intent;
+
+import android.preference.Preference;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.textservice.SpellCheckerInfo;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class SingleSpellCheckerPreference extends Preference {
+ private static final float DISABLED_ALPHA = 0.4f;
+
+ private final SpellCheckerInfo mSpellCheckerInfo;
+
+ private SettingsPreferenceFragment mFragment;
+ private TextView mTitleText;
+ private TextView mSummaryText;
+ private View mPrefAll;
+ private View mPrefLeftButton;
+ private ImageView mSetingsButton;
+ private Intent mSettingsIntent;
+ private boolean mSelected;
+
+ public SingleSpellCheckerPreference(SettingsPreferenceFragment fragment, Intent settingsIntent,
+ SpellCheckerInfo sci) {
+ super(fragment.getActivity(), null, 0);
+ setLayoutResource(R.layout.preference_spellchecker);
+ mSpellCheckerInfo = sci;
+ mSelected = false;
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ mPrefAll = view.findViewById(R.id.pref_all);
+ mPrefLeftButton = view.findViewById(R.id.pref_left_button);
+ mPrefLeftButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ onLeftButtonClicked(arg0);
+ }
+ });
+ mSetingsButton = (ImageView)view.findViewById(R.id.pref_right_button);
+ mTitleText = (TextView)view.findViewById(android.R.id.title);
+ mSummaryText = (TextView)view.findViewById(android.R.id.summary);
+ mSetingsButton.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ onSettingsButtonClicked(arg0);
+ }
+ });
+ updateSelectedState(mSelected);
+ }
+
+ private void onLeftButtonClicked(View arg0) {
+ final OnPreferenceClickListener listener = getOnPreferenceClickListener();
+ if (listener != null) {
+ listener.onPreferenceClick(this);
+ }
+ }
+
+ public SpellCheckerInfo getSpellCheckerInfo() {
+ return mSpellCheckerInfo;
+ }
+
+ public void updateSelectedState(boolean selected) {
+ if (mPrefAll != null) {
+ if (selected) {
+ // TODO: Use a color defined by the design guideline.
+ mPrefAll.setBackgroundColor(0x88006666);
+ } else {
+ mPrefAll.setBackgroundColor(0);
+ }
+ enableSettingsButton(selected);
+ }
+ }
+
+ public void setSelected(boolean selected) {
+ mSelected = selected;
+ }
+
+ protected void onSettingsButtonClicked(View arg0) {
+ if (mFragment != null && mSettingsIntent != null) {
+ mFragment.startActivity(mSettingsIntent);
+ }
+ }
+
+ private void enableSettingsButton(boolean enabled) {
+ if (mSetingsButton != null) {
+ if (mSettingsIntent == null) {
+ mSetingsButton.setVisibility(View.GONE);
+ } else {
+ mSetingsButton.setEnabled(enabled);
+ mSetingsButton.setClickable(enabled);
+ mSetingsButton.setFocusable(enabled);
+ if (!enabled) {
+ mSetingsButton.setAlpha(DISABLED_ALPHA);
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/inputmethod/SpellCheckerUtils.java b/src/com/android/settings/inputmethod/SpellCheckerUtils.java
new file mode 100644
index 0000000..fe761a6
--- /dev/null
+++ b/src/com/android/settings/inputmethod/SpellCheckerUtils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.util.Log;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.TextServicesManager;
+
+public class SpellCheckerUtils {
+ private static final String TAG = SpellCheckerUtils.class.getSimpleName();
+ private static final boolean DBG = false;
+ public static void setSpellCheckersEnabled(TextServicesManager tsm, boolean enable) {
+ }
+ public static boolean getSpellCheckersEnabled(TextServicesManager tsm) {
+ return true;
+ }
+ public static void setCurrentSpellChecker(TextServicesManager tsm, SpellCheckerInfo info) {
+ }
+ public static SpellCheckerInfo getCurrentSpellChecker(TextServicesManager tsm) {
+ final SpellCheckerInfo retval = tsm.getCurrentSpellChecker();
+ if (DBG) {
+ Log.d(TAG, "getCurrentSpellChecker: " + retval);
+ }
+ return retval;
+ }
+ public static SpellCheckerInfo[] getEnabledSpellCheckers(TextServicesManager tsm) {
+ final SpellCheckerInfo[] retval = tsm.getEnabledSpellCheckers();
+ if (DBG) {
+ Log.d(TAG, "get spell checkers: " + retval.length);
+ }
+ return retval;
+ }
+}
diff --git a/src/com/android/settings/inputmethod/SpellCheckersPreference.java b/src/com/android/settings/inputmethod/SpellCheckersPreference.java
new file mode 100644
index 0000000..7d2eec8
--- /dev/null
+++ b/src/com/android/settings/inputmethod/SpellCheckersPreference.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+public class SpellCheckersPreference extends CheckBoxAndSettingsPreference {
+
+ public SpellCheckersPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+}
diff --git a/src/com/android/settings/inputmethod/SpellCheckersSettings.java b/src/com/android/settings/inputmethod/SpellCheckersSettings.java
new file mode 100644
index 0000000..d6c0b1c
--- /dev/null
+++ b/src/com/android/settings/inputmethod/SpellCheckersSettings.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.TextServicesManager;
+
+import java.util.ArrayList;
+
+public class SpellCheckersSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceClickListener {
+
+ private SpellCheckerInfo mCurrentSci;
+ private SpellCheckerInfo[] mEnabledScis;
+ private TextServicesManager mTsm;
+ private final ArrayList<SingleSpellCheckerPreference> mSpellCheckers =
+ new ArrayList<SingleSpellCheckerPreference>();
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mTsm = (TextServicesManager) getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
+ addPreferencesFromResource(R.xml.spellchecker_prefs);
+ updateScreen();
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
+ return false;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateScreen();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ saveState();
+ }
+
+ private void saveState() {
+ SpellCheckerUtils.setCurrentSpellChecker(mTsm, mCurrentSci);
+ }
+
+ private void updateScreen() {
+ getPreferenceScreen().removeAll();
+ updateEnabledSpellCheckers();
+ }
+
+ private void updateEnabledSpellCheckers() {
+ mCurrentSci = SpellCheckerUtils.getCurrentSpellChecker(mTsm);
+ mEnabledScis = SpellCheckerUtils.getEnabledSpellCheckers(mTsm);
+ if (mCurrentSci == null || mEnabledScis == null) {
+ return;
+ }
+ mSpellCheckers.clear();
+ for (int i = 0; i < mEnabledScis.length; ++i) {
+ final SpellCheckerInfo sci = mEnabledScis[i];
+ final SingleSpellCheckerPreference scPref = new SingleSpellCheckerPreference(
+ this, null, sci);
+ mSpellCheckers.add(scPref);
+ scPref.setTitle(sci.getId());
+ scPref.setSelected(mCurrentSci != null && mCurrentSci.getId().equals(sci.getId()));
+ getPreferenceScreen().addPreference(scPref);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference arg0) {
+ for (SingleSpellCheckerPreference scp : mSpellCheckers) {
+ if (arg0.equals(scp)) {
+ scp.setSelected(true);
+ mTsm.setCurrentSpellChecker(scp.getSpellCheckerInfo());
+ } else {
+ scp.setSelected(false);
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/inputmethod/UserDictionaryList.java b/src/com/android/settings/inputmethod/UserDictionaryList.java
new file mode 100644
index 0000000..232a6db
--- /dev/null
+++ b/src/com/android/settings/inputmethod/UserDictionaryList.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.UserDictionarySettings;
+import com.android.settings.Utils;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.provider.UserDictionary;
+
+import java.util.Locale;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class UserDictionaryList extends SettingsPreferenceFragment {
+
+ private static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION =
+ "android.settings.USER_DICTIONARY_SETTINGS";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
+ }
+
+ static Set<String> getUserDictionaryLocalesList(Activity activity) {
+ @SuppressWarnings("deprecation")
+ final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
+ new String[] { UserDictionary.Words.LOCALE },
+ null, null, null);
+ final Set<String> localeList = new TreeSet<String>();
+ if (null == cursor) {
+ // The user dictionary service is not present or disabled. Return null.
+ return null;
+ } else if (cursor.moveToFirst()) {
+ final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
+ do {
+ String locale = cursor.getString(columnIndex);
+ localeList.add(null != locale ? locale : "");
+ } while (cursor.moveToNext());
+ }
+ localeList.add(Locale.getDefault().toString());
+ return localeList;
+ }
+
+ /**
+ * Creates the entries that allow the user to go into the user dictionary for each locale.
+ * @param userDictGroup The group to put the settings in.
+ */
+ protected void createUserDictSettings(PreferenceGroup userDictGroup) {
+ final Activity activity = getActivity();
+ userDictGroup.removeAll();
+ final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity);
+
+ if (localeList.isEmpty()) {
+ userDictGroup.addPreference(createUserDictionaryPreference(null, activity));
+ } else {
+ for (String locale : localeList) {
+ userDictGroup.addPreference(createUserDictionaryPreference(locale, activity));
+ }
+ }
+ }
+
+ /**
+ * Create a single User Dictionary Preference object, with its parameters set.
+ * @param locale The locale for which this user dictionary is for.
+ * @return The corresponding preference.
+ */
+ protected Preference createUserDictionaryPreference(String locale, Activity activity) {
+ final Preference newPref = new Preference(getActivity());
+ final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION);
+ if (null == locale) {
+ newPref.setTitle(Locale.getDefault().getDisplayName());
+ } else {
+ if ("".equals(locale))
+ newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
+ else
+ newPref.setTitle(Utils.createLocaleFromString(locale).getDisplayName());
+ intent.putExtra("locale", locale);
+ newPref.getExtras().putString("locale", locale);
+ }
+ newPref.setIntent(intent);
+ newPref.setFragment(UserDictionarySettings.class.getName());
+ return newPref;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ createUserDictSettings(getPreferenceScreen());
+ }
+}
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
new file mode 100644
index 0000000..81cf78e
--- /dev/null
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.net;
+
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.SNOOZE_NEVER;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.buildTemplateMobile3gLower;
+import static android.net.NetworkTemplate.buildTemplateMobile4g;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+
+import com.android.internal.util.Objects;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to modify list of {@link NetworkPolicy}. Specifically knows
+ * about which policies can coexist.
+ */
+public class NetworkPolicyEditor {
+ // TODO: be more robust when missing policies from service
+
+ private INetworkPolicyManager mPolicyService;
+ private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
+
+ public NetworkPolicyEditor(INetworkPolicyManager policyService) {
+ mPolicyService = checkNotNull(policyService);
+ }
+
+ public void read() {
+ try {
+ final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
+ mPolicies.clear();
+ for (NetworkPolicy policy : policies) {
+ // TODO: find better place to clamp these
+ if (policy.limitBytes < -1) {
+ policy.limitBytes = LIMIT_DISABLED;
+ }
+ if (policy.warningBytes < -1) {
+ policy.warningBytes = WARNING_DISABLED;
+ }
+
+ mPolicies.add(policy);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("problem reading policies", e);
+ }
+ }
+
+ public void writeAsync() {
+ // TODO: consider making more robust by passing through service
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ write();
+ return null;
+ }
+ }.execute();
+ }
+
+ public void write() {
+ try {
+ final NetworkPolicy[] policies = mPolicies.toArray(new NetworkPolicy[mPolicies.size()]);
+ mPolicyService.setNetworkPolicies(policies);
+ } catch (RemoteException e) {
+ throw new RuntimeException("problem reading policies", e);
+ }
+ }
+
+ public NetworkPolicy getPolicy(NetworkTemplate template) {
+ for (NetworkPolicy policy : mPolicies) {
+ if (policy.template.equals(template)) {
+ return policy;
+ }
+ }
+ return null;
+ }
+
+ public void setPolicyCycleDay(NetworkTemplate template, int cycleDay) {
+ final NetworkPolicy policy = getPolicy(template);
+ policy.cycleDay = cycleDay;
+ policy.lastSnooze = SNOOZE_NEVER;
+ writeAsync();
+ }
+
+ public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
+ final NetworkPolicy policy = getPolicy(template);
+ policy.warningBytes = warningBytes;
+ policy.lastSnooze = SNOOZE_NEVER;
+ writeAsync();
+ }
+
+ public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
+ final NetworkPolicy policy = getPolicy(template);
+ policy.limitBytes = limitBytes;
+ policy.lastSnooze = SNOOZE_NEVER;
+ writeAsync();
+ }
+
+ public boolean isMobilePolicySplit(String subscriberId) {
+ boolean has3g = false;
+ boolean has4g = false;
+ for (NetworkPolicy policy : mPolicies) {
+ final NetworkTemplate template = policy.template;
+ if (Objects.equal(subscriberId, template.getSubscriberId())) {
+ switch (template.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER:
+ has3g = true;
+ break;
+ case MATCH_MOBILE_4G:
+ has4g = true;
+ break;
+ }
+ }
+ }
+ return has3g && has4g;
+ }
+
+ public void setMobilePolicySplit(String subscriberId, boolean split) {
+ final boolean beforeSplit = isMobilePolicySplit(subscriberId);
+
+ final NetworkTemplate template3g = buildTemplateMobile3gLower(subscriberId);
+ final NetworkTemplate template4g = buildTemplateMobile4g(subscriberId);
+ final NetworkTemplate templateAll = buildTemplateMobileAll(subscriberId);
+
+ if (split == beforeSplit) {
+ // already in requested state; skip
+ return;
+
+ } else if (beforeSplit && !split) {
+ // combine, picking most restrictive policy
+ final NetworkPolicy policy3g = getPolicy(template3g);
+ final NetworkPolicy policy4g = getPolicy(template4g);
+
+ final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
+ : policy4g;
+ mPolicies.remove(policy3g);
+ mPolicies.remove(policy4g);
+ mPolicies.add(
+ new NetworkPolicy(templateAll, restrictive.cycleDay, restrictive.warningBytes,
+ restrictive.limitBytes, SNOOZE_NEVER));
+ writeAsync();
+
+ } else if (!beforeSplit && split) {
+ // duplicate existing policy into two rules
+ final NetworkPolicy policyAll = getPolicy(templateAll);
+ mPolicies.remove(policyAll);
+ mPolicies.add(
+ new NetworkPolicy(template3g, policyAll.cycleDay, policyAll.warningBytes,
+ policyAll.limitBytes, SNOOZE_NEVER));
+ mPolicies.add(
+ new NetworkPolicy(template4g, policyAll.cycleDay, policyAll.warningBytes,
+ policyAll.limitBytes, SNOOZE_NEVER));
+ writeAsync();
+
+ }
+ }
+
+}
diff --git a/src/com/android/settings/net/SummaryForAllUidLoader.java b/src/com/android/settings/net/SummaryForAllUidLoader.java
new file mode 100644
index 0000000..c01de45
--- /dev/null
+++ b/src/com/android/settings/net/SummaryForAllUidLoader.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.net;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+public class SummaryForAllUidLoader extends AsyncTaskLoader<NetworkStats> {
+ private static final String KEY_TEMPLATE = "template";
+ private static final String KEY_START = "start";
+ private static final String KEY_END = "end";
+
+ private final INetworkStatsService mStatsService;
+ private final Bundle mArgs;
+
+ public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
+ final Bundle args = new Bundle();
+ args.putParcelable(KEY_TEMPLATE, template);
+ args.putLong(KEY_START, start);
+ args.putLong(KEY_END, end);
+ return args;
+ }
+
+ public SummaryForAllUidLoader(
+ Context context, INetworkStatsService statsService, Bundle args) {
+ super(context);
+ mStatsService = statsService;
+ mArgs = args;
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ forceLoad();
+ }
+
+ @Override
+ public NetworkStats loadInBackground() {
+ final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
+ final long start = mArgs.getLong(KEY_START);
+ final long end = mArgs.getLong(KEY_END);
+
+ try {
+ return mStatsService.getSummaryForAllUid(template, start, end, false);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ cancelLoad();
+ }
+}
diff --git a/src/com/android/settings/nfc/NfcEnabler.java b/src/com/android/settings/nfc/NfcEnabler.java
index d1cec13..722787d 100644
--- a/src/com/android/settings/nfc/NfcEnabler.java
+++ b/src/com/android/settings/nfc/NfcEnabler.java
@@ -16,8 +16,6 @@
package com.android.settings.nfc;
-import com.android.settings.R;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -26,9 +24,11 @@ import android.nfc.NfcAdapter;
import android.os.Handler;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
-import android.provider.Settings;
+import android.preference.PreferenceScreen;
import android.util.Log;
+import com.android.settings.R;
+
/**
* NfcEnabler is a helper to manage the Nfc on/off checkbox preference. It is
* turns on/off Nfc and ensures the summary of the preference reflects the
@@ -39,6 +39,7 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener {
private final Context mContext;
private final CheckBoxPreference mCheckbox;
+ private final PreferenceScreen mZeroClick;
private final NfcAdapter mNfcAdapter;
private final IntentFilter mIntentFilter;
private final Handler mHandler = new Handler();
@@ -47,38 +48,37 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGE.equals(action)) {
- handleNfcStateChanged(intent.getBooleanExtra(
- NfcAdapter.EXTRA_NEW_BOOLEAN_STATE,
- false));
+ if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(action)) {
+ handleNfcStateChanged(intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
+ NfcAdapter.STATE_OFF));
}
}
};
- private boolean mNfcState;
-
- public NfcEnabler(Context context, CheckBoxPreference checkBoxPreference) {
+ public NfcEnabler(Context context, CheckBoxPreference checkBoxPreference,
+ PreferenceScreen zeroclick) {
mContext = context;
mCheckbox = checkBoxPreference;
+ mZeroClick = zeroclick;
mNfcAdapter = NfcAdapter.getDefaultAdapter(context);
if (mNfcAdapter == null) {
// NFC is not supported
mCheckbox.setEnabled(false);
+ mZeroClick.setEnabled(false);
+ mIntentFilter = null;
+ return;
}
-
- mIntentFilter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGE);
-
+ mIntentFilter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
}
public void resume() {
if (mNfcAdapter == null) {
return;
}
+ handleNfcStateChanged(mNfcAdapter.getAdapterState());
mContext.registerReceiver(mReceiver, mIntentFilter);
mCheckbox.setOnPreferenceChangeListener(this);
- mNfcState = mNfcAdapter.isEnabled();
- mCheckbox.setChecked(mNfcState);
}
public void pause() {
@@ -95,41 +95,43 @@ public class NfcEnabler implements Preference.OnPreferenceChangeListener {
final boolean desiredState = (Boolean) value;
mCheckbox.setEnabled(false);
- // Start async update of the NFC adapter state, as the API is
- // unfortunately blocking...
- new Thread("toggleNFC") {
- public void run() {
- Log.d(TAG, "Setting NFC enabled state to: " + desiredState);
- boolean success = false;
- if (desiredState) {
- success = mNfcAdapter.enable();
- } else {
- success = mNfcAdapter.disable();
- }
- if (success) {
- Log.d(TAG, "Successfully changed NFC enabled state to " + desiredState);
- mHandler.post(new Runnable() {
- public void run() {
- handleNfcStateChanged(desiredState);
- }
- });
- } else {
- Log.w(TAG, "Error setting NFC enabled state to " + desiredState);
- mHandler.post(new Runnable() {
- public void run() {
- mCheckbox.setEnabled(true);
- mCheckbox.setSummary(R.string.nfc_toggle_error);
- }
- });
- }
- }
- }.start();
+ if (desiredState) {
+ mNfcAdapter.enable();
+ } else {
+ mNfcAdapter.disable();
+ }
+
return false;
}
- private void handleNfcStateChanged(boolean newState) {
- mCheckbox.setChecked(newState);
- mCheckbox.setEnabled(true);
- mCheckbox.setSummary(R.string.nfc_quick_toggle_summary);
+ private void handleNfcStateChanged(int newState) {
+ switch (newState) {
+ case NfcAdapter.STATE_OFF:
+ mCheckbox.setChecked(false);
+ mCheckbox.setEnabled(true);
+ mZeroClick.setEnabled(false);
+ mZeroClick.setSummary(R.string.zeroclick_off_summary);
+ break;
+ case NfcAdapter.STATE_ON:
+ mCheckbox.setChecked(true);
+ mCheckbox.setEnabled(true);
+ mZeroClick.setEnabled(true);
+ if (mNfcAdapter.isZeroClickEnabled()) {
+ mZeroClick.setSummary(R.string.zeroclick_on_summary);
+ } else {
+ mZeroClick.setSummary(R.string.zeroclick_off_summary);
+ }
+ break;
+ case NfcAdapter.STATE_TURNING_ON:
+ mCheckbox.setChecked(true);
+ mCheckbox.setEnabled(false);
+ mZeroClick.setEnabled(false);
+ break;
+ case NfcAdapter.STATE_TURNING_OFF:
+ mCheckbox.setChecked(false);
+ mCheckbox.setEnabled(false);
+ mZeroClick.setEnabled(false);
+ break;
+ }
}
}
diff --git a/src/com/android/settings/nfc/ZeroClick.java b/src/com/android/settings/nfc/ZeroClick.java
new file mode 100644
index 0000000..1b59b52
--- /dev/null
+++ b/src/com/android/settings/nfc/ZeroClick.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.nfc;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.Fragment;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+import com.android.settings.R;
+
+public class ZeroClick extends Fragment
+ implements CompoundButton.OnCheckedChangeListener {
+ private View mView;
+ private NfcAdapter mNfcAdapter;
+ private Switch mActionBarSwitch;
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Activity activity = getActivity();
+
+ mActionBarSwitch = new Switch(activity);
+
+ if (activity instanceof PreferenceActivity) {
+ PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
+ final int padding = activity.getResources().getDimensionPixelSize(
+ R.dimen.action_bar_switch_padding);
+ mActionBarSwitch.setPadding(0, 0, padding, 0);
+ activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM);
+ activity.getActionBar().setCustomView(mActionBarSwitch, new ActionBar.LayoutParams(
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT));
+ activity.getActionBar().setTitle(R.string.zeroclick_settings_title);
+ }
+ }
+
+ mActionBarSwitch.setOnCheckedChangeListener(this);
+
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
+ mActionBarSwitch.setChecked(mNfcAdapter.isZeroClickEnabled());
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mView = inflater.inflate(R.layout.zeroclick, container, false);
+ initView(mView);
+ return mView;
+ }
+
+ private void initView(View view) {
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
+ mActionBarSwitch.setOnCheckedChangeListener(this);
+ mActionBarSwitch.setChecked(mNfcAdapter.isZeroClickEnabled());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean desiredState) {
+ boolean success = false;
+ mActionBarSwitch.setEnabled(false);
+ if (desiredState) {
+ success = mNfcAdapter.enableZeroClick();
+ } else {
+ success = mNfcAdapter.disableZeroClick();
+ }
+ if (success) {
+ mActionBarSwitch.setChecked(desiredState);
+ }
+ mActionBarSwitch.setEnabled(true);
+ }
+}
diff --git a/src/com/android/settings/vpn/AuthenticationActor.java b/src/com/android/settings/vpn/AuthenticationActor.java
deleted file mode 100644
index f8401d6..0000000
--- a/src/com/android/settings/vpn/AuthenticationActor.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.net.vpn.VpnManager;
-import android.net.vpn.VpnProfile;
-import android.net.vpn.VpnState;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.TextView;
-
-import java.io.IOException;
-
-/**
- * A {@link VpnProfileActor} that provides an authentication view for users to
- * input username and password before connecting to the VPN server.
- */
-public class AuthenticationActor implements VpnProfileActor {
- private static final String TAG = AuthenticationActor.class.getName();
-
- private Context mContext;
- private VpnProfile mProfile;
- private VpnManager mVpnManager;
-
- public AuthenticationActor(Context context, VpnProfile p) {
- mContext = context;
- mProfile = p;
- mVpnManager = new VpnManager(context);
- }
-
- //@Override
- public VpnProfile getProfile() {
- return mProfile;
- }
-
- //@Override
- public boolean isConnectDialogNeeded() {
- return true;
- }
-
- //@Override
- public String validateInputs(Dialog d) {
- TextView usernameView = (TextView) d.findViewById(R.id.username_value);
- TextView passwordView = (TextView) d.findViewById(R.id.password_value);
- Context c = mContext;
- if (TextUtils.isEmpty(usernameView.getText().toString())) {
- return c.getString(R.string.vpn_a_username);
- } else if (TextUtils.isEmpty(passwordView.getText().toString())) {
- return c.getString(R.string.vpn_a_password);
- } else {
- return null;
- }
- }
-
- //@Override
- public void connect(Dialog d) {
- TextView usernameView = (TextView) d.findViewById(R.id.username_value);
- TextView passwordView = (TextView) d.findViewById(R.id.password_value);
- CheckBox saveUsername = (CheckBox) d.findViewById(R.id.save_username);
-
- try {
- setSavedUsername(saveUsername.isChecked()
- ? usernameView.getText().toString()
- : "");
- } catch (IOException e) {
- Log.e(TAG, "setSavedUsername()", e);
- }
-
- connect(usernameView.getText().toString(),
- passwordView.getText().toString());
- passwordView.setText("");
- }
-
- //@Override
- public View createConnectView() {
- View v = View.inflate(mContext, R.layout.vpn_connect_dialog_view, null);
- TextView usernameView = (TextView) v.findViewById(R.id.username_value);
- TextView passwordView = (TextView) v.findViewById(R.id.password_value);
- CheckBox saveUsername = (CheckBox) v.findViewById(R.id.save_username);
-
- String username = mProfile.getSavedUsername();
- if (!TextUtils.isEmpty(username)) {
- usernameView.setText(username);
- saveUsername.setChecked(true);
- passwordView.requestFocus();
- }
- return v;
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- private void connect(String username, String password) {
- mVpnManager.connect(mProfile, username, password);
- }
-
- //@Override
- public void disconnect() {
- mVpnManager.disconnect();
- }
-
- private void setSavedUsername(String name) throws IOException {
- if (!name.equals(mProfile.getSavedUsername())) {
- mProfile.setSavedUsername(name);
- VpnSettings.saveProfileToStorage(mProfile);
- }
- }
-}
diff --git a/src/com/android/settings/vpn/L2tpEditor.java b/src/com/android/settings/vpn/L2tpEditor.java
deleted file mode 100644
index 05d51d6..0000000
--- a/src/com/android/settings/vpn/L2tpEditor.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-
-import android.content.Context;
-import android.net.vpn.L2tpProfile;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-
-/**
- * The class for editing {@link L2tpProfile}.
- */
-class L2tpEditor extends VpnProfileEditor {
- private CheckBoxPreference mSecret;
- private SecretHandler mSecretHandler;
-
- public L2tpEditor(L2tpProfile p) {
- super(p);
- }
-
- @Override
- protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
- Context c = subpanel.getContext();
- subpanel.addPreference(createSecretPreference(c));
- subpanel.addPreference(createSecretStringPreference(c));
-
- L2tpProfile profile = (L2tpProfile) getProfile();
- }
-
- @Override
- public String validate() {
- String result = super.validate();
- if (!mSecret.isChecked()) return result;
-
- return ((result != null) ? result : mSecretHandler.validate());
- }
-
- private Preference createSecretPreference(Context c) {
- final L2tpProfile profile = (L2tpProfile) getProfile();
- CheckBoxPreference secret = mSecret = new CheckBoxPreference(c);
- boolean enabled = profile.isSecretEnabled();
- setCheckBoxTitle(secret, R.string.vpn_l2tp_secret);
- secret.setChecked(enabled);
- setSecretSummary(secret, enabled);
- secret.setOnPreferenceChangeListener(
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- boolean enabled = (Boolean) newValue;
- profile.setSecretEnabled(enabled);
- mSecretHandler.getPreference().setEnabled(enabled);
- setSecretSummary(mSecret, enabled);
- return true;
- }
- });
- return secret;
- }
-
- private Preference createSecretStringPreference(Context c) {
- SecretHandler sHandler = mSecretHandler = new SecretHandler(c,
- R.string.vpn_l2tp_secret_string_title,
- R.string.vpn_l2tp_secret) {
- @Override
- protected String getSecretFromProfile() {
- return ((L2tpProfile) getProfile()).getSecretString();
- }
-
- @Override
- protected void saveSecretToProfile(String secret) {
- ((L2tpProfile) getProfile()).setSecretString(secret);
- }
- };
- Preference pref = sHandler.getPreference();
- pref.setEnabled(mSecret.isChecked());
- return pref;
- }
-
- private void setSecretSummary(CheckBoxPreference secret, boolean enabled) {
- Context c = secret.getContext();
- String formatString = c.getString(enabled
- ? R.string.vpn_is_enabled
- : R.string.vpn_is_disabled);
- secret.setSummary(String.format(
- formatString, c.getString(R.string.vpn_l2tp_secret)));
- }
-}
diff --git a/src/com/android/settings/vpn/L2tpIpsecEditor.java b/src/com/android/settings/vpn/L2tpIpsecEditor.java
deleted file mode 100644
index 276ee2f..0000000
--- a/src/com/android/settings/vpn/L2tpIpsecEditor.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-
-import android.content.Context;
-import android.net.vpn.L2tpIpsecProfile;
-import android.preference.EditTextPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.text.TextUtils;
-
-/**
- * The class for editing {@link L2tpIpsecProfile}.
- */
-class L2tpIpsecEditor extends L2tpEditor {
- private static final String TAG = L2tpIpsecEditor.class.getSimpleName();
-
- private KeyStore mKeyStore = KeyStore.getInstance();
-
- private ListPreference mUserCertificate;
- private ListPreference mCaCertificate;
-
- private L2tpIpsecProfile mProfile;
-
- public L2tpIpsecEditor(L2tpIpsecProfile p) {
- super(p);
- mProfile = p;
- }
-
- @Override
- protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
- super.loadExtraPreferencesTo(subpanel);
- Context c = subpanel.getContext();
- subpanel.addPreference(createUserCertificatePreference(c));
- subpanel.addPreference(createCaCertificatePreference(c));
- }
-
- @Override
- public String validate() {
- String result = super.validate();
- if (result == null) {
- result = validate(mUserCertificate, R.string.vpn_a_user_certificate);
- }
- if (result == null) {
- result = validate(mCaCertificate, R.string.vpn_a_ca_certificate);
- }
- return result;
- }
-
- private Preference createUserCertificatePreference(Context c) {
- mUserCertificate = createListPreference(c,
- R.string.vpn_user_certificate_title,
- mProfile.getUserCertificate(),
- mKeyStore.saw(Credentials.USER_CERTIFICATE),
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- mProfile.setUserCertificate((String) newValue);
- setSummary(pref, R.string.vpn_user_certificate,
- (String) newValue);
- return true;
- }
- });
- setSummary(mUserCertificate, R.string.vpn_user_certificate,
- mProfile.getUserCertificate());
- return mUserCertificate;
- }
-
- private Preference createCaCertificatePreference(Context c) {
- mCaCertificate = createListPreference(c,
- R.string.vpn_ca_certificate_title,
- mProfile.getCaCertificate(),
- mKeyStore.saw(Credentials.CA_CERTIFICATE),
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- mProfile.setCaCertificate((String) newValue);
- setSummary(pref, R.string.vpn_ca_certificate,
- (String) newValue);
- return true;
- }
- });
- setSummary(mCaCertificate, R.string.vpn_ca_certificate,
- mProfile.getCaCertificate());
- return mCaCertificate;
- }
-
- private ListPreference createListPreference(Context c, int titleResId,
- String text, String[] keys,
- Preference.OnPreferenceChangeListener listener) {
- ListPreference pref = new ListPreference(c);
- pref.setTitle(titleResId);
- pref.setDialogTitle(titleResId);
- pref.setPersistent(true);
- pref.setEntries(keys);
- pref.setEntryValues(keys);
- pref.setValue(text);
- pref.setOnPreferenceChangeListener(listener);
- return pref;
- }
-}
diff --git a/src/com/android/settings/vpn/L2tpIpsecPskEditor.java b/src/com/android/settings/vpn/L2tpIpsecPskEditor.java
deleted file mode 100644
index 1277c28..0000000
--- a/src/com/android/settings/vpn/L2tpIpsecPskEditor.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-
-import android.content.Context;
-import android.net.vpn.L2tpIpsecPskProfile;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-
-/**
- * The class for editing {@link L2tpIpsecPskProfile}.
- */
-class L2tpIpsecPskEditor extends L2tpEditor {
- private EditTextPreference mPresharedKey;
- private SecretHandler mPskHandler;
-
- public L2tpIpsecPskEditor(L2tpIpsecPskProfile p) {
- super(p);
- }
-
- @Override
- protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
- Context c = subpanel.getContext();
- subpanel.addPreference(createPresharedKeyPreference(c));
- super.loadExtraPreferencesTo(subpanel);
- }
-
- @Override
- public String validate() {
- String result = super.validate();
-
- return ((result != null) ? result : mPskHandler.validate());
- }
-
- private Preference createPresharedKeyPreference(Context c) {
- SecretHandler pskHandler = mPskHandler = new SecretHandler(c,
- R.string.vpn_ipsec_presharedkey_title,
- R.string.vpn_ipsec_presharedkey) {
- @Override
- protected String getSecretFromProfile() {
- return ((L2tpIpsecPskProfile) getProfile()).getPresharedKey();
- }
-
- @Override
- protected void saveSecretToProfile(String secret) {
- ((L2tpIpsecPskProfile) getProfile()).setPresharedKey(secret);
- }
- };
- return pskHandler.getPreference();
- }
-}
diff --git a/src/com/android/settings/vpn/PptpEditor.java b/src/com/android/settings/vpn/PptpEditor.java
deleted file mode 100644
index cfb3fa3..0000000
--- a/src/com/android/settings/vpn/PptpEditor.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/* * 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.vpn;
-
-import com.android.settings.R;
-
-import android.content.Context;
-import android.net.vpn.PptpProfile;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-
-/**
- * The class for editing {@link PptpProfile}.
- */
-class PptpEditor extends VpnProfileEditor {
- private CheckBoxPreference mEncryption;
-
- public PptpEditor(PptpProfile p) {
- super(p);
- }
-
- @Override
- protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
- Context c = subpanel.getContext();
- subpanel.addPreference(createEncryptionPreference(c));
-
- PptpProfile profile = (PptpProfile) getProfile();
- }
-
- private Preference createEncryptionPreference(Context c) {
- final PptpProfile profile = (PptpProfile) getProfile();
- CheckBoxPreference encryption = mEncryption = new CheckBoxPreference(c);
- boolean enabled = profile.isEncryptionEnabled();
- setCheckBoxTitle(encryption, R.string.vpn_pptp_encryption_title);
- encryption.setChecked(enabled);
- setEncryptionSummary(encryption, enabled);
- encryption.setOnPreferenceChangeListener(
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- boolean enabled = (Boolean) newValue;
- profile.setEncryptionEnabled(enabled);
- setEncryptionSummary(mEncryption, enabled);
- return true;
- }
- });
- return encryption;
- }
-
- private void setEncryptionSummary(CheckBoxPreference encryption,
- boolean enabled) {
- Context c = encryption.getContext();
- String formatString = c.getString(enabled
- ? R.string.vpn_is_enabled
- : R.string.vpn_is_disabled);
- encryption.setSummary(String.format(
- formatString, c.getString(R.string.vpn_pptp_encryption)));
- }
-}
diff --git a/src/com/android/settings/vpn/Util.java b/src/com/android/settings/vpn/Util.java
deleted file mode 100644
index a37049d..0000000
--- a/src/com/android/settings/vpn/Util.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.widget.Toast;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-class Util {
-
- static void showShortToastMessage(Context context, String message) {
- Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
- }
-
- static void showShortToastMessage(Context context, int messageId) {
- Toast.makeText(context, messageId, Toast.LENGTH_SHORT).show();
- }
-
- static void showLongToastMessage(Context context, String message) {
- Toast.makeText(context, message, Toast.LENGTH_LONG).show();
- }
-
- static void showLongToastMessage(Context context, int messageId) {
- Toast.makeText(context, messageId, Toast.LENGTH_LONG).show();
- }
-
- static void showErrorMessage(Context c, String message) {
- createErrorDialog(c, message, null).show();
- }
-
- static void showErrorMessage(Context c, String message,
- DialogInterface.OnClickListener listener) {
- createErrorDialog(c, message, listener).show();
- }
-
- static void deleteFile(String path) {
- deleteFile(new File(path));
- }
-
- static void deleteFile(String path, boolean toDeleteSelf) {
- deleteFile(new File(path), toDeleteSelf);
- }
-
- static void deleteFile(File f) {
- deleteFile(f, true);
- }
-
- static void deleteFile(File f, boolean toDeleteSelf) {
- if (f.isDirectory()) {
- for (File child : f.listFiles()) deleteFile(child, true);
- }
- if (toDeleteSelf) f.delete();
- }
-
- static boolean isFileOrEmptyDirectory(String path) {
- File f = new File(path);
- if (!f.isDirectory()) return true;
-
- String[] list = f.list();
- return ((list == null) || (list.length == 0));
- }
-
- static boolean copyFiles(String sourcePath , String targetPath)
- throws IOException {
- return copyFiles(new File(sourcePath), new File(targetPath));
- }
-
- // returns false if sourceLocation is the same as the targetLocation
- static boolean copyFiles(File sourceLocation , File targetLocation)
- throws IOException {
- if (sourceLocation.equals(targetLocation)) return false;
-
- if (sourceLocation.isDirectory()) {
- if (!targetLocation.exists()) {
- targetLocation.mkdir();
- }
- String[] children = sourceLocation.list();
- for (int i=0; i<children.length; i++) {
- copyFiles(new File(sourceLocation, children[i]),
- new File(targetLocation, children[i]));
- }
- } else if (sourceLocation.exists()) {
- InputStream in = new FileInputStream(sourceLocation);
- OutputStream out = new FileOutputStream(targetLocation);
-
- // Copy the bits from instream to outstream
- byte[] buf = new byte[1024];
- int len;
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
- in.close();
- out.close();
- }
- return true;
- }
-
- private static AlertDialog createErrorDialog(Context c, String message,
- DialogInterface.OnClickListener okListener) {
- AlertDialog.Builder b = new AlertDialog.Builder(c)
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(message);
- if (okListener != null) {
- b.setPositiveButton(R.string.vpn_back_button, okListener);
- } else {
- b.setPositiveButton(android.R.string.ok, null);
- }
- return b.create();
- }
-
- private Util() {
- }
-}
diff --git a/src/com/android/settings/vpn/VpnEditor.java b/src/com/android/settings/vpn/VpnEditor.java
deleted file mode 100644
index d362793..0000000
--- a/src/com/android/settings/vpn/VpnEditor.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.net.vpn.L2tpIpsecProfile;
-import android.net.vpn.L2tpIpsecPskProfile;
-import android.net.vpn.L2tpProfile;
-import android.net.vpn.PptpProfile;
-import android.net.vpn.VpnManager;
-import android.net.vpn.VpnProfile;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.preference.PreferenceActivity;
-import android.text.TextUtils;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.widget.Toast;
-
-/**
- * The activity class for editing a new or existing VPN profile.
- */
-public class VpnEditor extends SettingsPreferenceFragment {
- private static final int MENU_SAVE = Menu.FIRST;
- private static final int MENU_CANCEL = Menu.FIRST + 1;
- private static final int CONFIRM_DIALOG_ID = 0;
- private static final String KEY_PROFILE = "profile";
- private static final String KEY_ORIGINAL_PROFILE_NAME = "orig_profile_name";
-
- private VpnManager mVpnManager;
- private VpnProfileEditor mProfileEditor;
- private boolean mAddingProfile;
- private byte[] mOriginalProfileData;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Loads the XML preferences file
- addPreferencesFromResource(R.xml.vpn_edit);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mVpnManager = new VpnManager(getActivity());
-
- VpnProfile p;
- if (savedInstanceState != null) {
- p = (VpnProfile)savedInstanceState.getParcelable(KEY_PROFILE);
- } else {
- p = (VpnProfile)getArguments().getParcelable(VpnSettings.KEY_VPN_PROFILE);
- if (p == null) {
- p = getActivity().getIntent().getParcelableExtra(VpnSettings.KEY_VPN_PROFILE);
- }
- }
-
- Parcel parcel = Parcel.obtain();
- p.writeToParcel(parcel, 0);
- mOriginalProfileData = parcel.marshall();
- parcel.setDataPosition(0);
- VpnProfile profile = (VpnProfile) VpnProfile.CREATOR.createFromParcel(parcel);
-
- mProfileEditor = getEditor(profile);
- mAddingProfile = TextUtils.isEmpty(profile.getName());
-
- initViewFor(profile);
-
- registerForContextMenu(getListView());
- setHasOptionsMenu(true);
- }
-
- @Override
- public synchronized void onSaveInstanceState(Bundle outState) {
- if (mProfileEditor == null) return;
-
- outState.putParcelable(KEY_PROFILE, getProfile());
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- menu.add(0, MENU_SAVE, 0, R.string.vpn_menu_done)
- .setIcon(android.R.drawable.ic_menu_save);
- menu.add(0, MENU_CANCEL, 0,
- mAddingProfile ? R.string.vpn_menu_cancel
- : R.string.vpn_menu_revert)
- .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_SAVE:
- Intent resultIntent = validateAndGetResult();
- if (resultIntent != null) {
- PreferenceActivity activity =
- (PreferenceActivity) getActivity();
- if (!mVpnManager.isIdle()) {
- Toast.makeText(activity, R.string.service_busy,
- Toast.LENGTH_SHORT).show();
- } else {
- activity.finishPreferencePanel(this,
- Activity.RESULT_OK, resultIntent);
- }
- }
- return true;
-
- case MENU_CANCEL:
- if (profileChanged()) {
- showDialog(CONFIRM_DIALOG_ID);
- } else {
- finishFragment();
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- /*
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_BACK:
- if (validateAndSetResult()) finish();
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }*/
-
- private void initViewFor(VpnProfile profile) {
- mProfileEditor.loadPreferencesTo(getPreferenceScreen());
- }
-
- /* package */static String getTitle(Context context, VpnProfile profile, boolean adding) {
- String formatString = adding
- ? context.getString(R.string.vpn_edit_title_add)
- : context.getString(R.string.vpn_edit_title_edit);
- return String.format(formatString,
- profile.getType().getDisplayName());
- }
-
- /**
- * Checks the validity of the inputs and set the profile as result if valid.
- * @return true if the result is successfully set
- */
- private Intent validateAndGetResult() {
- String errorMsg = mProfileEditor.validate();
-
- if (errorMsg != null) {
- Util.showErrorMessage(getActivity(), errorMsg);
- return null;
- }
-
- if (profileChanged()) {
- return getResult(getProfile());
- }
- return null;
- }
-
- private Intent getResult(VpnProfile p) {
- Intent intent = new Intent(getActivity(), VpnSettings.class);
- intent.putExtra(VpnSettings.KEY_VPN_PROFILE, (Parcelable) p);
- return intent;
- }
-
- private VpnProfileEditor getEditor(VpnProfile p) {
- switch (p.getType()) {
- case L2TP_IPSEC:
- return new L2tpIpsecEditor((L2tpIpsecProfile) p);
-
- case L2TP_IPSEC_PSK:
- return new L2tpIpsecPskEditor((L2tpIpsecPskProfile) p);
-
- case L2TP:
- return new L2tpEditor((L2tpProfile) p);
-
- case PPTP:
- return new PptpEditor((PptpProfile) p);
-
- default:
- return new VpnProfileEditor(p);
- }
- }
-
-
- @Override
- public Dialog onCreateDialog(int id) {
- if (id == CONFIRM_DIALOG_ID) {
- return new AlertDialog.Builder(getActivity())
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(mAddingProfile
- ? R.string.vpn_confirm_add_profile_cancellation
- : R.string.vpn_confirm_edit_profile_cancellation)
- .setPositiveButton(R.string.vpn_yes_button,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int w) {
- finishFragment();
- }
- })
- .setNegativeButton(R.string.vpn_mistake_button, null)
- .create();
- }
-
- return super.onCreateDialog(id);
- }
-
- /*
- @Override
- public void onPrepareDialog(int id, Dialog dialog) {
- super.onPrepareDialog(id, dialog);
-
- if (id == CONFIRM_DIALOG_ID) {
- ((AlertDialog)dialog).setMessage(mAddingProfile
- ? getString(R.string.vpn_confirm_add_profile_cancellation)
- : getString(R.string.vpn_confirm_edit_profile_cancellation));
- }
- }*/
-
- private VpnProfile getProfile() {
- return mProfileEditor.getProfile();
- }
-
- private boolean profileChanged() {
- Parcel newParcel = Parcel.obtain();
- getProfile().writeToParcel(newParcel, 0);
- byte[] newData = newParcel.marshall();
- if (mOriginalProfileData.length == newData.length) {
- for (int i = 0, n = mOriginalProfileData.length; i < n; i++) {
- if (mOriginalProfileData[i] != newData[i]) return true;
- }
- return false;
- }
- return true;
- }
-}
diff --git a/src/com/android/settings/vpn/VpnProfileActor.java b/src/com/android/settings/vpn/VpnProfileActor.java
deleted file mode 100644
index 4c7b014..0000000
--- a/src/com/android/settings/vpn/VpnProfileActor.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.vpn;
-
-import android.app.Dialog;
-import android.net.vpn.VpnProfile;
-import android.view.View;
-
-/**
- * The interface to act on a {@link VpnProfile}.
- */
-public interface VpnProfileActor {
- VpnProfile getProfile();
-
- /**
- * Returns true if a connect dialog is needed before establishing a
- * connection.
- */
- boolean isConnectDialogNeeded();
-
- /**
- * Creates the view in the connect dialog.
- */
- View createConnectView();
-
- /**
- * Validates the inputs in the dialog.
- * @param dialog the connect dialog
- * @return an error message if the inputs are not valid
- */
- String validateInputs(Dialog dialog);
-
- /**
- * Establishes a VPN connection.
- * @param dialog the connect dialog
- */
- void connect(Dialog dialog);
-
- /**
- * Tears down the connection.
- */
- void disconnect();
-}
diff --git a/src/com/android/settings/vpn/VpnProfileEditor.java b/src/com/android/settings/vpn/VpnProfileEditor.java
deleted file mode 100644
index 100b78e..0000000
--- a/src/com/android/settings/vpn/VpnProfileEditor.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-
-import android.content.Context;
-import android.net.vpn.VpnProfile;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.method.PasswordTransformationMethod;
-
-/**
- * The common class for editing {@link VpnProfile}.
- */
-class VpnProfileEditor {
- private static final String KEY_VPN_NAME = "vpn_name";
-
- private EditTextPreference mName;
- private EditTextPreference mServerName;
- private EditTextPreference mDomainSuffices;
- private VpnProfile mProfile;
-
- public VpnProfileEditor(VpnProfile p) {
- mProfile = p;
- }
-
- //@Override
- public VpnProfile getProfile() {
- return mProfile;
- }
-
- /**
- * Adds the preferences to the panel. Subclasses should override
- * {@link #loadExtraPreferencesTo(PreferenceGroup)} instead of this method.
- */
- public void loadPreferencesTo(PreferenceGroup subpanel) {
- Context c = subpanel.getContext();
-
- mName = (EditTextPreference) subpanel.findPreference(KEY_VPN_NAME);
- mName.setOnPreferenceChangeListener(
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- setName((String) newValue);
- return true;
- }
- });
- setName(getProfile().getName());
- mName.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
- | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
-
- subpanel.addPreference(createServerNamePreference(c));
- loadExtraPreferencesTo(subpanel);
- subpanel.addPreference(createDomainSufficesPreference(c));
- }
-
- /**
- * Adds the extra preferences to the panel. Subclasses should add
- * additional preferences in this method.
- */
- protected void loadExtraPreferencesTo(PreferenceGroup subpanel) {
- }
-
- /**
- * Validates the inputs in the preferences.
- *
- * @return an error message that is ready to be displayed in a dialog; or
- * null if all the inputs are valid
- */
- public String validate() {
- String result = validate(mName, R.string.vpn_a_name);
- return ((result != null)
- ? result
- : validate(mServerName, R.string.vpn_a_vpn_server));
- }
-
- /**
- * Creates a preference for users to input domain suffices.
- */
- protected EditTextPreference createDomainSufficesPreference(Context c) {
- EditTextPreference pref = mDomainSuffices = createEditTextPreference(c,
- R.string.vpn_dns_search_list_title,
- R.string.vpn_dns_search_list,
- mProfile.getDomainSuffices(),
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- String v = ((String) newValue).trim();
- mProfile.setDomainSuffices(v);
- setSummary(pref, R.string.vpn_dns_search_list, v, false);
- return true;
- }
- });
- pref.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI);
- return pref;
- }
-
- private Preference createServerNamePreference(Context c) {
- EditTextPreference pref = mServerName = createEditTextPreference(c,
- R.string.vpn_vpn_server_title,
- R.string.vpn_vpn_server,
- mProfile.getServerName(),
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- String v = ((String) newValue).trim();
- mProfile.setServerName(v);
- setSummary(pref, R.string.vpn_vpn_server, v);
- return true;
- }
- });
- pref.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI);
- return pref;
- }
-
- protected EditTextPreference createEditTextPreference(Context c, int titleId,
- int prefNameId, String value,
- Preference.OnPreferenceChangeListener listener) {
- EditTextPreference pref = new EditTextPreference(c);
- pref.setTitle(titleId);
- pref.setDialogTitle(titleId);
- setSummary(pref, prefNameId, value);
- pref.setText(value);
- pref.setPersistent(true);
- pref.setOnPreferenceChangeListener(listener);
- return pref;
- }
-
- protected String validate(Preference pref, int fieldNameId) {
- Context c = pref.getContext();
- String value = (pref instanceof EditTextPreference)
- ? ((EditTextPreference) pref).getText()
- : ((ListPreference) pref).getValue();
- String formatString = (pref instanceof EditTextPreference)
- ? c.getString(R.string.vpn_error_miss_entering)
- : c.getString(R.string.vpn_error_miss_selecting);
- return (TextUtils.isEmpty(value)
- ? String.format(formatString, c.getString(fieldNameId))
- : null);
- }
-
- protected void setSummary(Preference pref, int fieldNameId, String v) {
- setSummary(pref, fieldNameId, v, true);
- }
-
- protected void setSummary(Preference pref, int fieldNameId, String v,
- boolean required) {
- Context c = pref.getContext();
- String formatString = required
- ? c.getString(R.string.vpn_field_not_set)
- : c.getString(R.string.vpn_field_not_set_optional);
- pref.setSummary(TextUtils.isEmpty(v)
- ? String.format(formatString, c.getString(fieldNameId))
- : v);
- }
-
- protected void setCheckBoxTitle(CheckBoxPreference pref, int fieldNameId) {
- Context c = pref.getContext();
- String formatString = c.getString(R.string.vpn_enable_field);
- pref.setTitle(String.format(formatString, c.getString(fieldNameId)));
- }
-
- private void setName(String newName) {
- newName = (newName == null) ? "" : newName.trim();
- mName.setText(newName);
- getProfile().setName(newName);
- setSummary(mName, R.string.vpn_name, newName);
- }
-
- // Secret is tricky to handle because empty field may mean "not set" or
- // "unchanged". This class hides that logic from callers.
- protected static abstract class SecretHandler {
- private EditTextPreference mPref;
- private int mFieldNameId;
- private boolean mHadSecret;
-
- protected SecretHandler(Context c, int titleId, int fieldNameId) {
- String value = getSecretFromProfile();
- mHadSecret = !TextUtils.isEmpty(value);
- mFieldNameId = fieldNameId;
-
- EditTextPreference pref = mPref = new EditTextPreference(c);
- pref.setTitle(titleId);
- pref.setDialogTitle(titleId);
- pref.getEditText().setInputType(
- InputType.TYPE_TEXT_VARIATION_PASSWORD);
- pref.getEditText().setTransformationMethod(
- new PasswordTransformationMethod());
- pref.setText("");
- pref.getEditText().setHint(mHadSecret
- ? R.string.vpn_secret_unchanged
- : R.string.vpn_secret_not_set);
- setSecretSummary(value);
- pref.setPersistent(true);
- saveSecretToProfile("");
- pref.setOnPreferenceChangeListener(
- new Preference.OnPreferenceChangeListener() {
- public boolean onPreferenceChange(
- Preference pref, Object newValue) {
- saveSecretToProfile((String) newValue);
- setSecretSummary((String) newValue);
- return true;
- }
- });
- }
-
- protected EditTextPreference getPreference() {
- return mPref;
- }
-
- protected String validate() {
- Context c = mPref.getContext();
- String value = mPref.getText();
- return ((TextUtils.isEmpty(value) && !mHadSecret)
- ? String.format(
- c.getString(R.string.vpn_error_miss_entering),
- c.getString(mFieldNameId))
- : null);
- }
-
- private void setSecretSummary(String value) {
- EditTextPreference pref = mPref;
- Context c = pref.getContext();
- String formatString = (TextUtils.isEmpty(value) && !mHadSecret)
- ? c.getString(R.string.vpn_field_not_set)
- : c.getString(R.string.vpn_field_is_set);
- pref.setSummary(
- String.format(formatString, c.getString(mFieldNameId)));
- }
-
- protected abstract String getSecretFromProfile();
- protected abstract void saveSecretToProfile(String secret);
- }
-}
diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java
deleted file mode 100644
index 5d75b6a..0000000
--- a/src/com/android/settings/vpn/VpnSettings.java
+++ /dev/null
@@ -1,1108 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-import android.app.Activity;
-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.net.vpn.L2tpIpsecProfile;
-import android.net.vpn.L2tpIpsecPskProfile;
-import android.net.vpn.L2tpProfile;
-import android.net.vpn.VpnManager;
-import android.net.vpn.VpnProfile;
-import android.net.vpn.VpnState;
-import android.net.vpn.VpnType;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceScreen;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The preference activity for configuring VPN settings.
- */
-public class VpnSettings extends SettingsPreferenceFragment
- implements DialogInterface.OnClickListener {
-
- private static final boolean DEBUG = false;
-
- // Key to the field exchanged for profile editing.
- static final String KEY_VPN_PROFILE = "vpn_profile";
-
- // Key to the field exchanged for VPN type selection.
- static final String KEY_VPN_TYPE = "vpn_type";
-
- private static final String TAG = VpnSettings.class.getSimpleName();
-
- 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.getProfilePath() + "/";
- private static final String PROFILE_OBJ_FILE = ".pobj";
-
- private static final String KEY_ACTIVE_PROFILE = "ActiveProfile";
- private static final String KEY_PROFILE_CONNECTING = "ProfileConnecting";
- private static final String KEY_CONNECT_DIALOG_SHOWING = "ConnectDialogShowing";
-
- private static final int REQUEST_ADD_OR_EDIT_PROFILE = 1;
- static final int REQUEST_SELECT_VPN_TYPE = 2;
-
- private static final int CONTEXT_MENU_CONNECT_ID = ContextMenu.FIRST + 0;
- private static final int CONTEXT_MENU_DISCONNECT_ID = ContextMenu.FIRST + 1;
- private static final int CONTEXT_MENU_EDIT_ID = ContextMenu.FIRST + 2;
- private static final int CONTEXT_MENU_DELETE_ID = ContextMenu.FIRST + 3;
-
- private static final int CONNECT_BUTTON = DialogInterface.BUTTON_POSITIVE;
- private static final int OK_BUTTON = DialogInterface.BUTTON_POSITIVE;
-
- private static final int DIALOG_CONNECT = VpnManager.VPN_ERROR_LARGEST + 1;
- private static final int DIALOG_SECRET_NOT_SET = DIALOG_CONNECT + 1;
-
- private static final int NO_ERROR = VpnManager.VPN_ERROR_NO_ERROR;
-
- private static final String KEY_PREFIX_IPSEC_PSK = Credentials.VPN + 'i';
- private static final String KEY_PREFIX_L2TP_SECRET = Credentials.VPN + 'l';
-
- private static List<VpnProfile> sVpnProfileList = new ArrayList<VpnProfile>();
-
- private PreferenceScreen mAddVpn;
- private PreferenceCategory mVpnListContainer;
-
- // profile name --> VpnPreference
- private Map<String, VpnPreference> mVpnPreferenceMap;
-
- // profile engaged in a connection
- private VpnProfile mActiveProfile;
-
- // actor engaged in connecting
- private VpnProfileActor mConnectingActor;
-
- // states saved for unlocking keystore
- private Runnable mUnlockAction;
-
- private KeyStore mKeyStore = KeyStore.getInstance();
-
- private VpnManager mVpnManager;
-
- private ConnectivityReceiver mConnectivityReceiver =
- new ConnectivityReceiver();
-
- private int mConnectingErrorCode = NO_ERROR;
-
- private Dialog mShowingDialog;
-
- private boolean mConnectDialogShowing = false;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.vpn_settings);
-
- mVpnManager = new VpnManager(getActivity());
- // restore VpnProfile list and construct VpnPreference map
- mVpnListContainer = (PreferenceCategory) findPreference(PREF_VPN_LIST);
-
- // set up the "add vpn" preference
- mAddVpn = (PreferenceScreen) findPreference(PREF_ADD_VPN);
- mAddVpn.setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
- public boolean onPreferenceClick(Preference preference) {
- startVpnTypeSelection();
- return true;
- }
- });
-
- retrieveVpnListFromStorage();
- restoreInstanceState(savedInstanceState);
- }
-
- @Override
- public void onSaveInstanceState(Bundle savedInstanceState) {
- if (mActiveProfile != null) {
- savedInstanceState.putString(KEY_ACTIVE_PROFILE,
- mActiveProfile.getId());
- savedInstanceState.putBoolean(KEY_PROFILE_CONNECTING,
- (mConnectingActor != null));
- savedInstanceState.putBoolean(KEY_CONNECT_DIALOG_SHOWING,
- mConnectDialogShowing);
- }
- super.onSaveInstanceState(savedInstanceState);
- }
-
- private void restoreInstanceState(Bundle savedInstanceState) {
- if (savedInstanceState == null) return;
- String profileId = savedInstanceState.getString(KEY_ACTIVE_PROFILE);
- if (profileId != null) {
- mActiveProfile = getProfile(getProfileIndexFromId(profileId));
- if (savedInstanceState.getBoolean(KEY_PROFILE_CONNECTING)) {
- mConnectingActor = getActor(mActiveProfile);
- }
- mConnectDialogShowing = savedInstanceState.getBoolean(KEY_CONNECT_DIALOG_SHOWING);
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- // for long-press gesture on a profile preference
- registerForContextMenu(getListView());
- }
-
- @Override
- public void onPause() {
- // ignore vpn connectivity event
- mVpnManager.unregisterConnectivityReceiver(mConnectivityReceiver);
- if ((mShowingDialog != null) && mShowingDialog.isShowing()) {
- mShowingDialog.dismiss();
- mShowingDialog = null;
- }
- super.onPause();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updatePreferenceMap();
-
- if (DEBUG) Log.d(TAG, "onResume");
-
- // listen to vpn connectivity event
- mVpnManager.registerConnectivityReceiver(mConnectivityReceiver);
-
- if ((mUnlockAction != null) && isKeyStoreUnlocked()) {
- Runnable action = mUnlockAction;
- mUnlockAction = null;
- getActivity().runOnUiThread(action);
- }
-
- if (!mConnectDialogShowing) {
- // If mActiveProfile is not null but it's in IDLE state, then a
- // retry dialog must be showing now as the previous connection
- // attempt failed. In this case, don't call checkVpnConnectionStatus()
- // as it will clean up mActiveProfile due to the IDLE state.
- if ((mActiveProfile == null)
- || (mActiveProfile.getState() != VpnState.IDLE)) {
- checkVpnConnectionStatus();
- }
- } else {
- // Dismiss the connect dialog in case there is another instance
- // trying to operate a vpn connection.
- if (!mVpnManager.isIdle() || (mActiveProfile == null)) {
- removeDialog(DIALOG_CONNECT);
- checkVpnConnectionStatus();
- }
- }
- }
-
- @Override
- public void onDestroyView() {
- unregisterForContextMenu(getListView());
- // This should be called after the procedure above as ListView inside this Fragment
- // will be deleted here.
- super.onDestroyView();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // Remove any onClick listeners
- if (mVpnListContainer != null) {
- for (int i = 0; i < mVpnListContainer.getPreferenceCount(); i++) {
- mVpnListContainer.getPreference(i).setOnPreferenceClickListener(null);
- }
- }
- }
-
- @Override
- public Dialog onCreateDialog (int id) {
- setOnCancelListener(new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- if (mActiveProfile != null) {
- changeState(mActiveProfile, VpnState.IDLE);
- }
- // Make sure onIdle() is called as the above changeState()
- // may not be effective if the state is already IDLE.
- // XXX: VpnService should broadcast non-IDLE state, say UNUSABLE,
- // when an error occurs.
- onIdle();
- }
- });
-
- switch (id) {
- case DIALOG_CONNECT:
- mConnectDialogShowing = true;
- setOnDismissListener(new DialogInterface.OnDismissListener() {
- public void onDismiss(DialogInterface dialog) {
- mConnectDialogShowing = false;
- }
- });
- return createConnectDialog();
-
- case DIALOG_SECRET_NOT_SET:
- return createSecretNotSetDialog();
-
- case VpnManager.VPN_ERROR_CHALLENGE:
- case VpnManager.VPN_ERROR_UNKNOWN_SERVER:
- case VpnManager.VPN_ERROR_PPP_NEGOTIATION_FAILED:
- return createEditDialog(id);
-
- default:
- Log.d(TAG, "create reconnect dialog for event " + id);
- return createReconnectDialog(id);
- }
- }
-
- private Dialog createConnectDialog() {
- final Activity activity = getActivity();
- return new AlertDialog.Builder(activity)
- .setView(mConnectingActor.createConnectView())
- .setTitle(String.format(activity.getString(R.string.vpn_connect_to),
- mConnectingActor.getProfile().getName()))
- .setPositiveButton(activity.getString(R.string.vpn_connect_button),
- this)
- .setNegativeButton(activity.getString(android.R.string.cancel),
- this)
- .create();
- }
-
- private Dialog createReconnectDialog(int id) {
- int msgId;
- switch (id) {
- case VpnManager.VPN_ERROR_AUTH:
- msgId = R.string.vpn_auth_error_dialog_msg;
- break;
-
- case VpnManager.VPN_ERROR_REMOTE_HUNG_UP:
- msgId = R.string.vpn_remote_hung_up_error_dialog_msg;
- break;
-
- case VpnManager.VPN_ERROR_CONNECTION_LOST:
- msgId = R.string.vpn_reconnect_from_lost;
- break;
-
- case VpnManager.VPN_ERROR_REMOTE_PPP_HUNG_UP:
- msgId = R.string.vpn_remote_ppp_hung_up_error_dialog_msg;
- break;
-
- default:
- msgId = R.string.vpn_confirm_reconnect;
- }
- return createCommonDialogBuilder().setMessage(msgId).create();
- }
-
- private Dialog createEditDialog(int id) {
- int msgId;
- switch (id) {
- case VpnManager.VPN_ERROR_CHALLENGE:
- msgId = R.string.vpn_challenge_error_dialog_msg;
- break;
-
- case VpnManager.VPN_ERROR_UNKNOWN_SERVER:
- msgId = R.string.vpn_unknown_server_dialog_msg;
- break;
-
- case VpnManager.VPN_ERROR_PPP_NEGOTIATION_FAILED:
- msgId = R.string.vpn_ppp_negotiation_failed_dialog_msg;
- break;
-
- default:
- return null;
- }
- return createCommonEditDialogBuilder().setMessage(msgId).create();
- }
-
- private Dialog createSecretNotSetDialog() {
- return createCommonDialogBuilder()
- .setMessage(R.string.vpn_secret_not_set_dialog_msg)
- .setPositiveButton(R.string.vpn_yes_button,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int w) {
- startVpnEditor(mActiveProfile, false);
- }
- })
- .create();
- }
-
- private AlertDialog.Builder createCommonEditDialogBuilder() {
- return createCommonDialogBuilder()
- .setPositiveButton(R.string.vpn_yes_button,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int w) {
- VpnProfile p = mActiveProfile;
- onIdle();
- startVpnEditor(p, false);
- }
- });
- }
-
- private AlertDialog.Builder createCommonDialogBuilder() {
- return new AlertDialog.Builder(getActivity())
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setPositiveButton(R.string.vpn_yes_button,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int w) {
- connectOrDisconnect(mActiveProfile);
- }
- })
- .setNegativeButton(R.string.vpn_no_button,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int w) {
- onIdle();
- }
- });
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- super.onCreateContextMenu(menu, v, menuInfo);
-
- VpnProfile p = getProfile(getProfilePositionFrom(
- (AdapterContextMenuInfo) menuInfo));
- if (p != null) {
- VpnState state = p.getState();
- menu.setHeaderTitle(p.getName());
-
- boolean isIdle = (state == VpnState.IDLE);
- boolean isNotConnect = (isIdle || (state == VpnState.DISCONNECTING)
- || (state == VpnState.CANCELLED));
- menu.add(0, CONTEXT_MENU_CONNECT_ID, 0, R.string.vpn_menu_connect)
- .setEnabled(isIdle && (mActiveProfile == null));
- menu.add(0, CONTEXT_MENU_DISCONNECT_ID, 0,
- R.string.vpn_menu_disconnect)
- .setEnabled(state == VpnState.CONNECTED);
- menu.add(0, CONTEXT_MENU_EDIT_ID, 0, R.string.vpn_menu_edit)
- .setEnabled(isNotConnect);
- menu.add(0, CONTEXT_MENU_DELETE_ID, 0, R.string.vpn_menu_delete)
- .setEnabled(isNotConnect);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- int position = getProfilePositionFrom(
- (AdapterContextMenuInfo) item.getMenuInfo());
- VpnProfile p = getProfile(position);
-
- switch(item.getItemId()) {
- case CONTEXT_MENU_CONNECT_ID:
- case CONTEXT_MENU_DISCONNECT_ID:
- connectOrDisconnect(p);
- return true;
-
- case CONTEXT_MENU_EDIT_ID:
- startVpnEditor(p, false);
- return true;
-
- case CONTEXT_MENU_DELETE_ID:
- deleteProfile(position);
- return true;
- }
-
- return super.onContextItemSelected(item);
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode,
- final Intent data) {
-
- if (DEBUG) Log.d(TAG, "onActivityResult , result = " + resultCode + ", data = " + data);
- if ((resultCode == Activity.RESULT_CANCELED) || (data == null)) {
- Log.d(TAG, "no result returned by editor");
- return;
- }
-
- if (requestCode == REQUEST_SELECT_VPN_TYPE) {
- final String typeName = data.getStringExtra(KEY_VPN_TYPE);
- startVpnEditor(createVpnProfile(typeName), true);
- } else if (requestCode == REQUEST_ADD_OR_EDIT_PROFILE) {
- VpnProfile p = data.getParcelableExtra(KEY_VPN_PROFILE);
- if (p == null) {
- Log.e(TAG, "null object returned by editor");
- return;
- }
-
- final Activity activity = getActivity();
- int index = getProfileIndexFromId(p.getId());
- if (checkDuplicateName(p, index)) {
- final VpnProfile profile = p;
- Util.showErrorMessage(activity, String.format(
- activity.getString(R.string.vpn_error_duplicate_name),
- p.getName()),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int w) {
- startVpnEditor(profile, false);
- }
- });
- return;
- }
-
- if (needKeyStoreToSave(p)) {
- Runnable action = new Runnable() {
- public void run() {
- onActivityResult(requestCode, resultCode, data);
- }
- };
- if (!unlockKeyStore(p, action)) return;
- }
-
- try {
- if (index < 0) {
- addProfile(p);
- Util.showShortToastMessage(activity, String.format(
- activity.getString(R.string.vpn_profile_added), p.getName()));
- } else {
- replaceProfile(index, p);
- Util.showShortToastMessage(activity, String.format(
- activity.getString(R.string.vpn_profile_replaced),
- p.getName()));
- }
- } catch (IOException e) {
- final VpnProfile profile = p;
- Util.showErrorMessage(activity, e + ": " + e.getMessage(),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int w) {
- startVpnEditor(profile, false);
- }
- });
- }
-
- // Remove cached VpnEditor as it is needless anymore.
- } else {
- throw new RuntimeException("unknown request code: " + requestCode);
- }
- }
-
- // Called when the buttons on the connect dialog are clicked.
- @Override
- public synchronized void onClick(DialogInterface dialog, int which) {
- if (which == CONNECT_BUTTON) {
- Dialog d = (Dialog) dialog;
- String error = mConnectingActor.validateInputs(d);
- if (error == null) {
- mConnectingActor.connect(d);
- } else {
- // show error dialog
- final Activity activity = getActivity();
- mShowingDialog = new AlertDialog.Builder(activity)
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(String.format(activity.getString(
- R.string.vpn_error_miss_entering), error))
- .setPositiveButton(R.string.vpn_back_button,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int which) {
- showDialog(DIALOG_CONNECT);
- }
- })
- .create();
- // The profile state is "connecting". If we allow the dialog to
- // be cancelable, then we need to clear the state in the
- // onCancel handler.
- mShowingDialog.setCancelable(false);
- mShowingDialog.show();
- }
- } else {
- changeState(mActiveProfile, VpnState.IDLE);
- }
- }
-
- private int getProfileIndexFromId(String id) {
- int index = 0;
- for (VpnProfile p : sVpnProfileList) {
- if (p.getId().equals(id)) {
- return index;
- } else {
- index++;
- }
- }
- return -1;
- }
-
- // Replaces the profile at index in sVpnProfileList with p.
- // Returns true if p's name is a duplicate.
- private boolean checkDuplicateName(VpnProfile p, int index) {
- List<VpnProfile> list = sVpnProfileList;
- VpnPreference pref = mVpnPreferenceMap.get(p.getName());
- if ((pref != null) && (index >= 0) && (index < list.size())) {
- // not a duplicate if p is to replace the profile at index
- if (pref.mProfile == list.get(index)) pref = null;
- }
- return (pref != null);
- }
-
- private int getProfilePositionFrom(AdapterContextMenuInfo menuInfo) {
- // excludes mVpnListContainer and the preferences above it
- return menuInfo.position - mVpnListContainer.getOrder() - 1;
- }
-
- // position: position in sVpnProfileList
- private VpnProfile getProfile(int position) {
- return ((position >= 0) ? sVpnProfileList.get(position) : null);
- }
-
- // position: position in sVpnProfileList
- private void deleteProfile(final int position) {
- if ((position < 0) || (position >= sVpnProfileList.size())) return;
- final VpnProfile target = sVpnProfileList.get(position);
- DialogInterface.OnClickListener onClickListener =
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Double check if the target is still the one we want
- // to remove.
- VpnProfile p = sVpnProfileList.get(position);
- if (p != target) return;
- if (which == OK_BUTTON) {
- sVpnProfileList.remove(position);
- VpnPreference pref =
- mVpnPreferenceMap.remove(p.getName());
- mVpnListContainer.removePreference(pref);
- removeProfileFromStorage(p);
- }
- }
- };
- mShowingDialog = new AlertDialog.Builder(getActivity())
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.vpn_confirm_profile_deletion)
- .setPositiveButton(android.R.string.ok, onClickListener)
- .setNegativeButton(R.string.vpn_no_button, onClickListener)
- .create();
- mShowingDialog.show();
- }
-
- // Randomly generates an ID for the profile.
- // The ID is unique and only set once when the profile is created.
- private void setProfileId(VpnProfile profile) {
- String id;
-
- while (true) {
- id = String.valueOf(Math.abs(
- Double.doubleToLongBits(Math.random())));
- if (id.length() >= 8) break;
- }
- for (VpnProfile p : sVpnProfileList) {
- if (p.getId().equals(id)) {
- setProfileId(profile);
- return;
- }
- }
- profile.setId(id);
- }
-
- private void addProfile(VpnProfile p) throws IOException {
- setProfileId(p);
- processSecrets(p);
- saveProfileToStorage(p);
-
- sVpnProfileList.add(p);
- addPreferenceFor(p, true);
- disableProfilePreferencesIfOneActive();
- }
-
- // Adds a preference in mVpnListContainer
- private VpnPreference addPreferenceFor(
- VpnProfile p, boolean addToContainer) {
- VpnPreference pref = new VpnPreference(getActivity(), p);
- mVpnPreferenceMap.put(p.getName(), pref);
- if (addToContainer) mVpnListContainer.addPreference(pref);
-
- pref.setOnPreferenceClickListener(
- new Preference.OnPreferenceClickListener() {
- public boolean onPreferenceClick(Preference pref) {
- connectOrDisconnect(((VpnPreference) pref).mProfile);
- return true;
- }
- });
- return pref;
- }
-
- // index: index to sVpnProfileList
- private void replaceProfile(int index, VpnProfile p) throws IOException {
- Map<String, VpnPreference> map = mVpnPreferenceMap;
- VpnProfile oldProfile = sVpnProfileList.set(index, p);
- VpnPreference pref = map.remove(oldProfile.getName());
- if (pref.mProfile != oldProfile) {
- throw new RuntimeException("inconsistent state!");
- }
-
- p.setId(oldProfile.getId());
-
- processSecrets(p);
-
- // TODO: remove copyFiles once the setId() code propagates.
- // Copy config files and remove the old ones if they are in different
- // directories.
- if (Util.copyFiles(getProfileDir(oldProfile), getProfileDir(p))) {
- removeProfileFromStorage(oldProfile);
- }
- saveProfileToStorage(p);
-
- pref.setProfile(p);
- map.put(p.getName(), pref);
- }
-
- private void startVpnTypeSelection() {
- if ((getActivity() == null) || isRemoving()) return;
-
- ((PreferenceActivity) getActivity()).startPreferencePanel(
- VpnTypeSelection.class.getCanonicalName(), null, R.string.vpn_type_title, null,
- this, REQUEST_SELECT_VPN_TYPE);
- }
-
- private boolean isKeyStoreUnlocked() {
- return mKeyStore.test() == KeyStore.NO_ERROR;
- }
-
- // Returns true if the profile needs to access keystore
- private boolean needKeyStoreToSave(VpnProfile p) {
- switch (p.getType()) {
- case L2TP_IPSEC_PSK:
- L2tpIpsecPskProfile pskProfile = (L2tpIpsecPskProfile) p;
- String presharedKey = pskProfile.getPresharedKey();
- if (!TextUtils.isEmpty(presharedKey)) return true;
- // $FALL-THROUGH$
- case L2TP:
- L2tpProfile l2tpProfile = (L2tpProfile) p;
- if (l2tpProfile.isSecretEnabled() &&
- !TextUtils.isEmpty(l2tpProfile.getSecretString())) {
- return true;
- }
- // $FALL-THROUGH$
- default:
- return false;
- }
- }
-
- // Returns true if the profile needs to access keystore
- private boolean needKeyStoreToConnect(VpnProfile p) {
- switch (p.getType()) {
- case L2TP_IPSEC:
- case L2TP_IPSEC_PSK:
- return true;
-
- case L2TP:
- return ((L2tpProfile) p).isSecretEnabled();
-
- default:
- return false;
- }
- }
-
- // Returns true if keystore is unlocked or keystore is not a concern
- private boolean unlockKeyStore(VpnProfile p, Runnable action) {
- if (isKeyStoreUnlocked()) return true;
- mUnlockAction = action;
- Credentials.getInstance().unlock(getActivity());
- return false;
- }
-
- private void startVpnEditor(final VpnProfile profile, boolean add) {
- if ((getActivity() == null) || isRemoving()) return;
-
- Bundle args = new Bundle();
- args.putParcelable(KEY_VPN_PROFILE, profile);
- // TODO: Show different titles for add and edit.
- ((PreferenceActivity)getActivity()).startPreferencePanel(
- VpnEditor.class.getCanonicalName(), args,
- 0, VpnEditor.getTitle(getActivity(), profile, add),
- this, REQUEST_ADD_OR_EDIT_PROFILE);
- }
-
- private synchronized void connect(final VpnProfile p) {
- if (needKeyStoreToConnect(p)) {
- Runnable action = new Runnable() {
- public void run() {
- connect(p);
- }
- };
- if (!unlockKeyStore(p, action)) return;
- }
-
- if (!checkSecrets(p)) return;
- changeState(p, VpnState.CONNECTING);
- if (mConnectingActor.isConnectDialogNeeded()) {
- showDialog(DIALOG_CONNECT);
- } else {
- mConnectingActor.connect(null);
- }
- }
-
- // Do connect or disconnect based on the current state.
- private synchronized void connectOrDisconnect(VpnProfile p) {
- VpnPreference pref = mVpnPreferenceMap.get(p.getName());
- switch (p.getState()) {
- case IDLE:
- connect(p);
- break;
-
- case CONNECTING:
- // do nothing
- break;
-
- case CONNECTED:
- case DISCONNECTING:
- changeState(p, VpnState.DISCONNECTING);
- getActor(p).disconnect();
- break;
- }
- }
-
- private void changeState(VpnProfile p, VpnState state) {
- VpnState oldState = p.getState();
- p.setState(state);
- mVpnPreferenceMap.get(p.getName()).setSummary(
- getProfileSummaryString(p));
-
- switch (state) {
- case CONNECTED:
- mConnectingActor = null;
- mActiveProfile = p;
- disableProfilePreferencesIfOneActive();
- break;
-
- case CONNECTING:
- if (mConnectingActor == null) {
- mConnectingActor = getActor(p);
- }
- // $FALL-THROUGH$
- case DISCONNECTING:
- mActiveProfile = p;
- disableProfilePreferencesIfOneActive();
- break;
-
- case CANCELLED:
- changeState(p, VpnState.IDLE);
- break;
-
- case IDLE:
- assert(mActiveProfile == p);
-
- if (mConnectingErrorCode == NO_ERROR) {
- onIdle();
- } else {
- showDialog(mConnectingErrorCode);
- mConnectingErrorCode = NO_ERROR;
- }
- break;
- }
- }
-
- private void onIdle() {
- if (DEBUG) Log.d(TAG, " onIdle()");
- mActiveProfile = null;
- mConnectingActor = null;
- enableProfilePreferences();
- }
-
- private void disableProfilePreferencesIfOneActive() {
- if (mActiveProfile == null) return;
-
- for (VpnProfile p : sVpnProfileList) {
- switch (p.getState()) {
- case CONNECTING:
- case DISCONNECTING:
- case IDLE:
- mVpnPreferenceMap.get(p.getName()).setEnabled(false);
- break;
-
- default:
- mVpnPreferenceMap.get(p.getName()).setEnabled(true);
- }
- }
- }
-
- private void enableProfilePreferences() {
- for (VpnProfile p : sVpnProfileList) {
- mVpnPreferenceMap.get(p.getName()).setEnabled(true);
- }
- }
-
- static String getProfileDir(VpnProfile p) {
- return PROFILES_ROOT + p.getId();
- }
-
- static void saveProfileToStorage(VpnProfile p) throws IOException {
- File f = new File(getProfileDir(p));
- if (!f.exists()) f.mkdirs();
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
- new File(f, PROFILE_OBJ_FILE)));
- oos.writeObject(p);
- oos.close();
- }
-
- private void removeProfileFromStorage(VpnProfile p) {
- Util.deleteFile(getProfileDir(p));
- }
-
- private void updatePreferenceMap() {
- mVpnPreferenceMap = new LinkedHashMap<String, VpnPreference>();
- mVpnListContainer.removeAll();
- for (VpnProfile p : sVpnProfileList) {
- addPreferenceFor(p, true);
- }
- // reset the mActiveProfile if the profile has been removed from the
- // other instance.
- if ((mActiveProfile != null)
- && !mVpnPreferenceMap.containsKey(mActiveProfile.getName())) {
- onIdle();
- }
- }
-
- private void retrieveVpnListFromStorage() {
- // skip the loop if the profile is loaded already.
- if (sVpnProfileList.size() > 0) return;
- File root = new File(PROFILES_ROOT);
- String[] dirs = root.list();
- if (dirs == null) return;
- for (String dir : dirs) {
- File f = new File(new File(root, dir), PROFILE_OBJ_FILE);
- if (!f.exists()) continue;
- try {
- VpnProfile p = deserialize(f);
- if (p == null) continue;
- if (!checkIdConsistency(dir, p)) continue;
-
- sVpnProfileList.add(p);
- } catch (IOException e) {
- Log.e(TAG, "retrieveVpnListFromStorage()", e);
- }
- }
- Collections.sort(sVpnProfileList, new Comparator<VpnProfile>() {
- public int compare(VpnProfile p1, VpnProfile p2) {
- return p1.getName().compareTo(p2.getName());
- }
- });
- disableProfilePreferencesIfOneActive();
- }
-
- private void checkVpnConnectionStatus() {
- for (VpnProfile p : sVpnProfileList) {
- changeState(p, mVpnManager.getState(p));
- }
- }
-
- // A sanity check. Returns true if the profile directory name and profile ID
- // are consistent.
- private boolean checkIdConsistency(String dirName, VpnProfile p) {
- if (!dirName.equals(p.getId())) {
- Log.d(TAG, "ID inconsistent: " + dirName + " vs " + p.getId());
- return false;
- } else {
- return true;
- }
- }
-
- private VpnProfile deserialize(File profileObjectFile) throws IOException {
- try {
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
- profileObjectFile));
- VpnProfile p = (VpnProfile) ois.readObject();
- ois.close();
- return p;
- } catch (ClassNotFoundException e) {
- Log.d(TAG, "deserialize a profile", e);
- return null;
- }
- }
-
- private String getProfileSummaryString(VpnProfile p) {
- final Activity activity = getActivity();
- switch (p.getState()) {
- case CONNECTING:
- return activity.getString(R.string.vpn_connecting);
- case DISCONNECTING:
- return activity.getString(R.string.vpn_disconnecting);
- case CONNECTED:
- return activity.getString(R.string.vpn_connected);
- default:
- return activity.getString(R.string.vpn_connect_hint);
- }
- }
-
- private VpnProfileActor getActor(VpnProfile p) {
- return new AuthenticationActor(getActivity(), p);
- }
-
- private VpnProfile createVpnProfile(String type) {
- return mVpnManager.createVpnProfile(Enum.valueOf(VpnType.class, type));
- }
-
- private boolean checkSecrets(VpnProfile p) {
- boolean secretMissing = false;
-
- if (p instanceof L2tpIpsecProfile) {
- L2tpIpsecProfile certProfile = (L2tpIpsecProfile) p;
-
- String cert = certProfile.getCaCertificate();
- if (TextUtils.isEmpty(cert) ||
- !mKeyStore.contains(Credentials.CA_CERTIFICATE + cert)) {
- certProfile.setCaCertificate(null);
- secretMissing = true;
- }
-
- cert = certProfile.getUserCertificate();
- if (TextUtils.isEmpty(cert) ||
- !mKeyStore.contains(Credentials.USER_CERTIFICATE + cert)) {
- certProfile.setUserCertificate(null);
- secretMissing = true;
- }
- }
-
- if (p instanceof L2tpIpsecPskProfile) {
- L2tpIpsecPskProfile pskProfile = (L2tpIpsecPskProfile) p;
- String presharedKey = pskProfile.getPresharedKey();
- String key = KEY_PREFIX_IPSEC_PSK + p.getId();
- if (TextUtils.isEmpty(presharedKey) || !mKeyStore.contains(key)) {
- pskProfile.setPresharedKey(null);
- secretMissing = true;
- }
- }
-
- if (p instanceof L2tpProfile) {
- L2tpProfile l2tpProfile = (L2tpProfile) p;
- if (l2tpProfile.isSecretEnabled()) {
- String secret = l2tpProfile.getSecretString();
- String key = KEY_PREFIX_L2TP_SECRET + p.getId();
- if (TextUtils.isEmpty(secret) || !mKeyStore.contains(key)) {
- l2tpProfile.setSecretString(null);
- secretMissing = true;
- }
- }
- }
-
- if (secretMissing) {
- mActiveProfile = p;
- showDialog(DIALOG_SECRET_NOT_SET);
- return false;
- } else {
- return true;
- }
- }
-
- private void processSecrets(VpnProfile p) {
- switch (p.getType()) {
- case L2TP_IPSEC_PSK:
- L2tpIpsecPskProfile pskProfile = (L2tpIpsecPskProfile) p;
- String presharedKey = pskProfile.getPresharedKey();
- String key = KEY_PREFIX_IPSEC_PSK + p.getId();
- if (!TextUtils.isEmpty(presharedKey) &&
- !mKeyStore.put(key, presharedKey)) {
- Log.e(TAG, "keystore write failed: key=" + key);
- }
- pskProfile.setPresharedKey(key);
- // $FALL-THROUGH$
- case L2TP_IPSEC:
- case L2TP:
- L2tpProfile l2tpProfile = (L2tpProfile) p;
- key = KEY_PREFIX_L2TP_SECRET + p.getId();
- if (l2tpProfile.isSecretEnabled()) {
- String secret = l2tpProfile.getSecretString();
- if (!TextUtils.isEmpty(secret) &&
- !mKeyStore.put(key, secret)) {
- Log.e(TAG, "keystore write failed: key=" + key);
- }
- l2tpProfile.setSecretString(key);
- } else {
- mKeyStore.delete(key);
- }
- break;
- }
- }
-
- private class VpnPreference extends Preference {
- VpnProfile mProfile;
- VpnPreference(Context c, VpnProfile p) {
- super(c);
- setProfile(p);
- }
-
- void setProfile(VpnProfile p) {
- mProfile = p;
- setTitle(p.getName());
- setSummary(getProfileSummaryString(p));
- }
- }
-
- // to receive vpn connectivity events broadcast by VpnService
- private class ConnectivityReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String profileName = intent.getStringExtra(
- VpnManager.BROADCAST_PROFILE_NAME);
- if (profileName == null) return;
-
- VpnState s = (VpnState) intent.getSerializableExtra(
- VpnManager.BROADCAST_CONNECTION_STATE);
-
- if (s == null) {
- Log.e(TAG, "received null connectivity state");
- return;
- }
-
- mConnectingErrorCode = intent.getIntExtra(
- VpnManager.BROADCAST_ERROR_CODE, NO_ERROR);
-
- VpnPreference pref = mVpnPreferenceMap.get(profileName);
- if (pref != null) {
- Log.d(TAG, "received connectivity: " + profileName
- + ": connected? " + s
- + " err=" + mConnectingErrorCode);
- // XXX: VpnService should broadcast non-IDLE state, say UNUSABLE,
- // when an error occurs.
- changeState(pref.mProfile, s);
- } else {
- Log.e(TAG, "received connectivity: " + profileName
- + ": connected? " + s + ", but profile does not exist;"
- + " just ignore it");
- }
- }
- }
-}
diff --git a/src/com/android/settings/vpn/VpnTypeSelection.java b/src/com/android/settings/vpn/VpnTypeSelection.java
deleted file mode 100644
index 45e33b9..0000000
--- a/src/com/android/settings/vpn/VpnTypeSelection.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.vpn;
-
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.vpn.VpnManager;
-import android.net.vpn.VpnType;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceScreen;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * The activity to select a VPN type.
- */
-public class VpnTypeSelection extends SettingsPreferenceFragment {
- private Map<String, VpnType> mTypeMap = new HashMap<String, VpnType>();
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.vpn_type);
- initTypeList();
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen ps, Preference pref) {
- ((PreferenceActivity)getActivity())
- .finishPreferencePanel(this, Activity.RESULT_OK,
- getResultIntent(mTypeMap.get(pref.getTitle().toString())));
- return true;
- }
-
- private void initTypeList() {
- PreferenceScreen root = getPreferenceScreen();
- final Activity activity = getActivity();
- for (VpnType t : VpnManager.getSupportedVpnTypes()) {
- String displayName = t.getDisplayName();
- String message = String.format(
- activity.getString(R.string.vpn_edit_title_add), displayName);
- mTypeMap.put(message, t);
-
- Preference pref = new Preference(activity);
- pref.setTitle(message);
- pref.setSummary(t.getDescriptionId());
- root.addPreference(pref);
- }
- }
-
- private Intent getResultIntent(VpnType type) {
- Intent intent = new Intent(getActivity(), VpnSettings.class);
- intent.putExtra(VpnSettings.KEY_VPN_TYPE, type.toString());
- return intent;
- }
-}
diff --git a/src/com/android/settings/vpn2/VpnDialog.java b/src/com/android/settings/vpn2/VpnDialog.java
new file mode 100644
index 0000000..cce5e2d
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnDialog.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.vpn2;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.net.InetAddress;
+
+class VpnDialog extends AlertDialog implements TextWatcher,
+ View.OnClickListener, AdapterView.OnItemSelectedListener {
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private final DialogInterface.OnClickListener mListener;
+ private final VpnProfile mProfile;
+
+ private boolean mEditing;
+
+ private View mView;
+
+ private TextView mName;
+ private Spinner mType;
+ private TextView mServer;
+ private TextView mUsername;
+ private TextView mPassword;
+ private TextView mSearchDomains;
+ private TextView mDnsServers;
+ private TextView mRoutes;
+ private CheckBox mMppe;
+ private TextView mL2tpSecret;
+ private TextView mIpsecIdentifier;
+ private TextView mIpsecSecret;
+ private Spinner mIpsecUserCert;
+ private Spinner mIpsecCaCert;
+ private CheckBox mSaveLogin;
+
+ VpnDialog(Context context, DialogInterface.OnClickListener listener,
+ VpnProfile profile, boolean editing) {
+ super(context);
+ mListener = listener;
+ mProfile = profile;
+ mEditing = editing;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
+ setView(mView);
+ setInverseBackgroundForced(true);
+
+ Context context = getContext();
+
+ // First, find out all the fields.
+ mName = (TextView) mView.findViewById(R.id.name);
+ mType = (Spinner) mView.findViewById(R.id.type);
+ mServer = (TextView) mView.findViewById(R.id.server);
+ mUsername = (TextView) mView.findViewById(R.id.username);
+ mPassword = (TextView) mView.findViewById(R.id.password);
+ mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
+ mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
+ mRoutes = (TextView) mView.findViewById(R.id.routes);
+ mMppe = (CheckBox) mView.findViewById(R.id.mppe);
+ mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
+ mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
+ mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
+ mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
+ mIpsecCaCert = (Spinner) mView.findViewById(R.id.ipsec_ca_cert);
+ mSaveLogin = (CheckBox) mView.findViewById(R.id.save_login);
+
+ // Second, copy values from the profile.
+ mName.setText(mProfile.name);
+ mType.setSelection(mProfile.type);
+ mServer.setText(mProfile.server);
+ mUsername.setText(mProfile.username);
+ mPassword.setText(mProfile.password);
+ mSearchDomains.setText(mProfile.searchDomains);
+ mDnsServers.setText(mProfile.dnsServers);
+ mRoutes.setText(mProfile.routes);
+ mMppe.setChecked(mProfile.mppe);
+ mL2tpSecret.setText(mProfile.l2tpSecret);
+ mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
+ mIpsecSecret.setText(mProfile.ipsecSecret);
+ loadCertificates(mIpsecUserCert, Credentials.USER_CERTIFICATE,
+ 0, mProfile.ipsecUserCert);
+ loadCertificates(mIpsecCaCert, Credentials.CA_CERTIFICATE,
+ R.string.vpn_no_ca_cert, mProfile.ipsecCaCert);
+ mSaveLogin.setChecked(mProfile.saveLogin);
+
+ // Third, add listeners to required fields.
+ mName.addTextChangedListener(this);
+ mType.setOnItemSelectedListener(this);
+ mServer.addTextChangedListener(this);
+ mUsername.addTextChangedListener(this);
+ mPassword.addTextChangedListener(this);
+ mDnsServers.addTextChangedListener(this);
+ mRoutes.addTextChangedListener(this);
+ mIpsecSecret.addTextChangedListener(this);
+ mIpsecUserCert.setOnItemSelectedListener(this);
+
+ // Forth, determine to do editing or connecting.
+ boolean valid = validate(true);
+ mEditing = mEditing || !valid;
+
+ if (mEditing) {
+ setTitle(R.string.vpn_edit);
+
+ // Show common fields.
+ mView.findViewById(R.id.editor).setVisibility(View.VISIBLE);
+
+ // Show type-specific fields.
+ changeType(mProfile.type);
+
+ // Show advanced options directly if any of them is set.
+ View showOptions = mView.findViewById(R.id.show_options);
+ if (mProfile.searchDomains.isEmpty() && mProfile.dnsServers.isEmpty() &&
+ mProfile.routes.isEmpty()) {
+ showOptions.setOnClickListener(this);
+ } else {
+ onClick(showOptions);
+ }
+
+ // Create a button to save the profile.
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(R.string.vpn_save), mListener);
+ } else {
+ setTitle(context.getString(R.string.vpn_connect_to, mProfile.name));
+
+ // Not editing, just show username and password.
+ mView.findViewById(R.id.login).setVisibility(View.VISIBLE);
+
+ // Create a button to connect the network.
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(R.string.vpn_connect), mListener);
+ }
+
+ // Always provide a cancel button.
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(R.string.vpn_cancel), mListener);
+
+ // Let AlertDialog create everything.
+ super.onCreate(null);
+
+ // Disable the action button if necessary.
+ getButton(DialogInterface.BUTTON_POSITIVE)
+ .setEnabled(mEditing ? valid : validate(false));
+
+ // Workaround to resize the dialog for the input method.
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+ }
+
+ @Override
+ public void afterTextChanged(Editable field) {
+ getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void onClick(View showOptions) {
+ showOptions.setVisibility(View.GONE);
+ mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (parent == mType) {
+ changeType(position);
+ }
+ getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validate(mEditing));
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+
+ private void changeType(int type) {
+ // First, hide everything.
+ mMppe.setVisibility(View.GONE);
+ mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
+ mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
+ mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
+ mView.findViewById(R.id.ipsec_ca).setVisibility(View.GONE);
+
+ // Then, unhide type-specific fields.
+ switch (type) {
+ case VpnProfile.TYPE_PPTP:
+ mMppe.setVisibility(View.VISIBLE);
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
+ // fall through
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ mView.findViewById(R.id.ipsec_ca).setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ private boolean validate(boolean editing) {
+ if (!editing) {
+ return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
+ }
+ if (mName.getText().length() == 0 || mServer.getText().length() == 0 ||
+ !validateAddresses(mDnsServers.getText().toString(), false) ||
+ !validateAddresses(mRoutes.getText().toString(), true)) {
+ return false;
+ }
+ switch (mType.getSelectedItemPosition()) {
+ case VpnProfile.TYPE_PPTP:
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ return true;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ return mIpsecSecret.getText().length() != 0;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ return mIpsecUserCert.getSelectedItemPosition() != 0;
+ }
+ return false;
+ }
+
+ private boolean validateAddresses(String addresses, boolean cidr) {
+ try {
+ for (String address : addresses.split(" ")) {
+ if (address.isEmpty()) {
+ continue;
+ }
+ // Legacy VPN currently only supports IPv4.
+ int prefixLength = 32;
+ if (cidr) {
+ String[] parts = address.split("/", 2);
+ address = parts[0];
+ prefixLength = Integer.parseInt(parts[1]);
+ }
+ byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
+ int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
+ (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
+ if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
+ (prefixLength < 32 && (integer << prefixLength) != 0)) {
+ return false;
+ }
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
+ Context context = getContext();
+ String first = (firstId == 0) ? "" : context.getString(firstId);
+ String[] certificates = mKeyStore.saw(prefix);
+
+ if (certificates == null || certificates.length == 0) {
+ certificates = new String[] {first};
+ } else {
+ String[] array = new String[certificates.length + 1];
+ array[0] = first;
+ System.arraycopy(certificates, 0, array, 1, certificates.length);
+ certificates = array;
+ }
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+ context, android.R.layout.simple_spinner_item, certificates);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+
+ for (int i = 1; i < certificates.length; ++i) {
+ if (certificates[i].equals(selected)) {
+ spinner.setSelection(i);
+ break;
+ }
+ }
+ }
+
+ boolean isEditing() {
+ return mEditing;
+ }
+
+ VpnProfile getProfile() {
+ // First, save common fields.
+ VpnProfile profile = new VpnProfile(mProfile.key);
+ profile.name = mName.getText().toString();
+ profile.type = mType.getSelectedItemPosition();
+ profile.server = mServer.getText().toString().trim();
+ profile.username = mUsername.getText().toString();
+ profile.password = mPassword.getText().toString();
+ profile.searchDomains = mSearchDomains.getText().toString().trim();
+ profile.dnsServers = mDnsServers.getText().toString().trim();
+ profile.routes = mRoutes.getText().toString().trim();
+
+ // Then, save type-specific fields.
+ switch (profile.type) {
+ case VpnProfile.TYPE_PPTP:
+ profile.mppe = mMppe.isChecked();
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ profile.l2tpSecret = mL2tpSecret.getText().toString();
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
+ profile.ipsecSecret = mIpsecSecret.getText().toString();
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ profile.l2tpSecret = mL2tpSecret.getText().toString();
+ // fall through
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ if (mIpsecUserCert.getSelectedItemPosition() != 0) {
+ profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
+ }
+ // fall through
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ if (mIpsecCaCert.getSelectedItemPosition() != 0) {
+ profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
+ }
+ break;
+ }
+
+ profile.saveLogin = mSaveLogin.isChecked();
+ return profile;
+ }
+}
diff --git a/src/com/android/settings/vpn2/VpnProfile.java b/src/com/android/settings/vpn2/VpnProfile.java
new file mode 100644
index 0000000..3ee3af0
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnProfile.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.vpn2;
+
+import java.nio.charset.Charsets;
+
+/**
+ * Parcel-like entity class for VPN profiles. To keep things simple, all
+ * fields are package private. Methods are provided for serialization, so
+ * storage can be implemented easily. Two rules are set for this class.
+ * First, all fields must be kept non-null. Second, always make a copy
+ * using clone() before modifying.
+ */
+class VpnProfile implements Cloneable {
+ // Match these constants with R.array.vpn_types.
+ static final int TYPE_PPTP = 0;
+ static final int TYPE_L2TP_IPSEC_PSK = 1;
+ static final int TYPE_L2TP_IPSEC_RSA = 2;
+ static final int TYPE_IPSEC_XAUTH_PSK = 3;
+ static final int TYPE_IPSEC_XAUTH_RSA = 4;
+ static final int TYPE_IPSEC_HYBRID_RSA = 5;
+ static final int TYPE_MAX = 5;
+
+ // Entity fields.
+ final String key; // -1
+ String name = ""; // 0
+ int type = TYPE_PPTP; // 1
+ String server = ""; // 2
+ String username = ""; // 3
+ String password = ""; // 4
+ String dnsServers = ""; // 5
+ String searchDomains = ""; // 6
+ String routes = ""; // 7
+ boolean mppe = true; // 8
+ String l2tpSecret = ""; // 9
+ String ipsecIdentifier = "";// 10
+ String ipsecSecret = ""; // 11
+ String ipsecUserCert = ""; // 12
+ String ipsecCaCert = ""; // 13
+
+ // Helper fields.
+ boolean saveLogin = false;
+
+ VpnProfile(String key) {
+ this.key = key;
+ }
+
+ static VpnProfile decode(String key, byte[] value) {
+ try {
+ if (key == null) {
+ return null;
+ }
+
+ String[] values = new String(value, Charsets.UTF_8).split("\0", -1);
+ // Currently it always has 14 fields.
+ if (values.length < 14) {
+ return null;
+ }
+
+ VpnProfile profile = new VpnProfile(key);
+ profile.name = values[0];
+ profile.type = Integer.valueOf(values[1]);
+ if (profile.type < 0 || profile.type > TYPE_MAX) {
+ return null;
+ }
+ profile.server = values[2];
+ profile.username = values[3];
+ profile.password = values[4];
+ profile.dnsServers = values[5];
+ profile.searchDomains = values[6];
+ profile.routes = values[7];
+ profile.mppe = Boolean.valueOf(values[8]);
+ profile.l2tpSecret = values[9];
+ profile.ipsecIdentifier = values[10];
+ profile.ipsecSecret = values[11];
+ profile.ipsecUserCert = values[12];
+ profile.ipsecCaCert = values[13];
+
+ profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
+ return profile;
+ } catch (Exception e) {
+ // ignore
+ }
+ return null;
+ }
+
+ byte[] encode() {
+ StringBuilder builder = new StringBuilder(name);
+ builder.append('\0').append(type);
+ builder.append('\0').append(server);
+ builder.append('\0').append(saveLogin ? username : "");
+ builder.append('\0').append(saveLogin ? password : "");
+ builder.append('\0').append(dnsServers);
+ builder.append('\0').append(searchDomains);
+ builder.append('\0').append(routes);
+ builder.append('\0').append(mppe);
+ builder.append('\0').append(l2tpSecret);
+ builder.append('\0').append(ipsecIdentifier);
+ builder.append('\0').append(ipsecSecret);
+ builder.append('\0').append(ipsecUserCert);
+ builder.append('\0').append(ipsecCaCert);
+ return builder.toString().getBytes(Charsets.UTF_8);
+ }
+}
diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java
new file mode 100644
index 0000000..0197333
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.vpn2;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.IConnectivityManager;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.net.Inet4Address;
+import java.nio.charset.Charsets;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class VpnSettings extends SettingsPreferenceFragment implements
+ Handler.Callback, Preference.OnPreferenceClickListener,
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+
+ private static final String TAG = "VpnSettings";
+
+ private final IConnectivityManager mService = IConnectivityManager.Stub
+ .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+ private boolean mUnlocking = false;
+
+ private HashMap<String, VpnPreference> mPreferences;
+ private VpnDialog mDialog;
+
+ private Handler mUpdater;
+ private LegacyVpnInfo mInfo;
+
+ // The key of the profile for the current ContextMenu.
+ private String mSelectedKey;
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ addPreferencesFromResource(R.xml.vpn_settings2);
+ getPreferenceScreen().setOrderingAsAdded(false);
+
+ if (savedState != null) {
+ VpnProfile profile = VpnProfile.decode(savedState.getString("VpnKey"),
+ savedState.getByteArray("VpnProfile"));
+ if (profile != null) {
+ mDialog = new VpnDialog(getActivity(), this, profile,
+ savedState.getBoolean("VpnEditing"));
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedState) {
+ // We do not save view hierarchy, as they are just profiles.
+ if (mDialog != null) {
+ VpnProfile profile = mDialog.getProfile();
+ savedState.putString("VpnKey", profile.key);
+ savedState.putByteArray("VpnProfile", profile.encode());
+ savedState.putBoolean("VpnEditing", mDialog.isEditing());
+ }
+ // else?
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Check KeyStore here, so others do not need to deal with it.
+ if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ if (!mUnlocking) {
+ // Let us unlock KeyStore. See you later!
+ Credentials.getInstance().unlock(getActivity());
+ } else {
+ // We already tried, but it is still not working!
+ finishFragment();
+ }
+ mUnlocking = !mUnlocking;
+ return;
+ }
+
+ // Now KeyStore is always unlocked. Reset the flag.
+ mUnlocking = false;
+
+ // Currently we are the only user of profiles in KeyStore.
+ // Assuming KeyStore and KeyGuard do the right thing, we can
+ // safely cache profiles in the memory.
+ if (mPreferences == null) {
+ mPreferences = new HashMap<String, VpnPreference>();
+ PreferenceGroup group = getPreferenceScreen();
+
+ String[] keys = mKeyStore.saw(Credentials.VPN);
+ if (keys != null && keys.length > 0) {
+ Context context = getActivity();
+
+ for (String key : keys) {
+ VpnProfile profile = VpnProfile.decode(key,
+ mKeyStore.get(Credentials.VPN + key));
+ if (profile == null) {
+ Log.w(TAG, "bad profile: key = " + key);
+ mKeyStore.delete(Credentials.VPN + key);
+ } else {
+ VpnPreference preference = new VpnPreference(context, profile);
+ mPreferences.put(key, preference);
+ group.addPreference(preference);
+ }
+ }
+ }
+ group.findPreference("add_network").setOnPreferenceClickListener(this);
+ }
+
+ // Show the dialog if there is one.
+ if (mDialog != null) {
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ }
+
+ // Start monitoring.
+ if (mUpdater == null) {
+ mUpdater = new Handler(this);
+ }
+ mUpdater.sendEmptyMessage(0);
+
+ // Register for context menu. Hmmm, getListView() is hidden?
+ registerForContextMenu(getListView());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Hide the dialog if there is one.
+ if (mDialog != null) {
+ mDialog.setOnDismissListener(null);
+ mDialog.dismiss();
+ }
+
+ // Unregister for context menu.
+ if (getView() != null) {
+ unregisterForContextMenu(getListView());
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ // Here is the exit of a dialog.
+ mDialog = null;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int button) {
+ if (button == DialogInterface.BUTTON_POSITIVE) {
+ // Always save the profile.
+ VpnProfile profile = mDialog.getProfile();
+ mKeyStore.put(Credentials.VPN + profile.key, profile.encode());
+
+ // Update the preference.
+ VpnPreference preference = mPreferences.get(profile.key);
+ if (preference != null) {
+ disconnect(profile.key);
+ preference.update(profile);
+ } else {
+ preference = new VpnPreference(getActivity(), profile);
+ mPreferences.put(profile.key, preference);
+ getPreferenceScreen().addPreference(preference);
+ }
+
+ // If we are not editing, connect!
+ if (!mDialog.isEditing()) {
+ try {
+ connect(profile);
+ } catch (Exception e) {
+ Log.e(TAG, "connect", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
+ if (mDialog != null) {
+ Log.v(TAG, "onCreateContextMenu() is called when mDialog != null");
+ return;
+ }
+
+ if (info instanceof AdapterContextMenuInfo) {
+ Preference preference = (Preference) getListView().getItemAtPosition(
+ ((AdapterContextMenuInfo) info).position);
+ if (preference instanceof VpnPreference) {
+ VpnProfile profile = ((VpnPreference) preference).getProfile();
+ mSelectedKey = profile.key;
+ menu.setHeaderTitle(profile.name);
+ menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit);
+ menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete);
+ }
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (mDialog != null) {
+ Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
+ return false;
+ }
+
+ VpnPreference preference = mPreferences.get(mSelectedKey);
+ if (preference == null) {
+ Log.v(TAG, "onContextItemSelected() is called but no preference is found");
+ return false;
+ }
+
+ switch (item.getItemId()) {
+ case R.string.vpn_menu_edit:
+ mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ return true;
+ case R.string.vpn_menu_delete:
+ disconnect(mSelectedKey);
+ getPreferenceScreen().removePreference(preference);
+ mPreferences.remove(mSelectedKey);
+ mKeyStore.delete(Credentials.VPN + mSelectedKey);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (mDialog != null) {
+ Log.v(TAG, "onPreferenceClick() is called when mDialog != null");
+ return true;
+ }
+
+ if (preference instanceof VpnPreference) {
+ VpnProfile profile = ((VpnPreference) preference).getProfile();
+ if (mInfo != null && profile.key.equals(mInfo.key) &&
+ mInfo.state == LegacyVpnInfo.STATE_CONNECTED) {
+ try {
+ mInfo.intent.send();
+ return true;
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ mDialog = new VpnDialog(getActivity(), this, profile, false);
+ } else {
+ // Generate a new key. Here we just use the current time.
+ long millis = System.currentTimeMillis();
+ while (mPreferences.containsKey(Long.toHexString(millis))) {
+ ++millis;
+ }
+ mDialog = new VpnDialog(getActivity(), this,
+ new VpnProfile(Long.toHexString(millis)), true);
+ }
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ return true;
+ }
+
+ @Override
+ public boolean handleMessage(Message message) {
+ mUpdater.removeMessages(0);
+
+ if (isResumed()) {
+ try {
+ LegacyVpnInfo info = mService.getLegacyVpnInfo();
+ if (mInfo != null) {
+ VpnPreference preference = mPreferences.get(mInfo.key);
+ if (preference != null) {
+ preference.update(-1);
+ }
+ mInfo = null;
+ }
+ if (info != null) {
+ VpnPreference preference = mPreferences.get(info.key);
+ if (preference != null) {
+ preference.update(info.state);
+ mInfo = info;
+ }
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ mUpdater.sendEmptyMessageDelayed(0, 1000);
+ }
+ return true;
+ }
+
+ private String[] getDefaultNetwork() throws Exception {
+ LinkProperties network = mService.getActiveLinkProperties();
+ if (network == null) {
+ throw new IllegalStateException("Network is not available");
+ }
+ String interfaze = network.getInterfaceName();
+ if (interfaze == null) {
+ throw new IllegalStateException("Cannot get the default interface");
+ }
+ String gateway = null;
+ for (RouteInfo route : network.getRoutes()) {
+ // Currently legacy VPN only works on IPv4.
+ if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
+ gateway = route.getGateway().getHostAddress();
+ break;
+ }
+ }
+ if (gateway == null) {
+ throw new IllegalStateException("Cannot get the default gateway");
+ }
+ return new String[] {interfaze, gateway};
+ }
+
+ private void connect(VpnProfile profile) throws Exception {
+ // Get the default interface and the default gateway.
+ String[] network = getDefaultNetwork();
+ String interfaze = network[0];
+ String gateway = network[1];
+
+ // Load certificates.
+ String privateKey = "";
+ String userCert = "";
+ String caCert = "";
+ if (!profile.ipsecUserCert.isEmpty()) {
+ byte[] value = mKeyStore.get(Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert);
+ privateKey = (value == null) ? null : new String(value, Charsets.UTF_8);
+ value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
+ userCert = (value == null) ? null : new String(value, Charsets.UTF_8);
+ }
+ if (!profile.ipsecCaCert.isEmpty()) {
+ byte[] value = mKeyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
+ caCert = (value == null) ? null : new String(value, Charsets.UTF_8);
+ }
+ if (privateKey == null || userCert == null || caCert == null) {
+ // TODO: find out a proper way to handle this. Delete these keys?
+ throw new IllegalStateException("Cannot load credentials");
+ }
+
+ // Prepare arguments for racoon.
+ String[] racoon = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ racoon = new String[] {
+ interfaze, profile.server, "udppsk", profile.ipsecIdentifier,
+ profile.ipsecSecret, "1701",
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ racoon = new String[] {
+ interfaze, profile.server, "udprsa", privateKey, userCert, caCert, "1701",
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ racoon = new String[] {
+ interfaze, profile.server, "xauthpsk", profile.ipsecIdentifier,
+ profile.ipsecSecret, profile.username, profile.password, "", gateway,
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ racoon = new String[] {
+ interfaze, profile.server, "xauthrsa", privateKey, userCert, caCert,
+ profile.username, profile.password, "", gateway,
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ racoon = new String[] {
+ interfaze, profile.server, "hybridrsa", caCert,
+ profile.username, profile.password, "", gateway,
+ };
+ break;
+ }
+
+ // Prepare arguments for mtpd.
+ String[] mtpd = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_PPTP:
+ mtpd = new String[] {
+ interfaze, "pptp", profile.server, "1723",
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ (profile.mppe ? "+mppe" : "nomppe"),
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ mtpd = new String[] {
+ interfaze, "l2tp", profile.server, "1701", profile.l2tpSecret,
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ };
+ break;
+ }
+
+ VpnConfig config = new VpnConfig();
+ config.user = profile.key;
+ config.interfaze = interfaze;
+ config.session = profile.name;
+ config.routes = profile.routes;
+ if (!profile.dnsServers.isEmpty()) {
+ config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
+ }
+ if (!profile.searchDomains.isEmpty()) {
+ config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
+ }
+
+ mService.startLegacyVpn(config, racoon, mtpd);
+ }
+
+ private void disconnect(String key) {
+ if (mInfo != null && key.equals(mInfo.key)) {
+ try {
+ mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+
+ private class VpnPreference extends Preference {
+ private VpnProfile mProfile;
+ private int mState = -1;
+
+ VpnPreference(Context context, VpnProfile profile) {
+ super(context);
+ setPersistent(false);
+ setOrder(0);
+ setOnPreferenceClickListener(VpnSettings.this);
+
+ mProfile = profile;
+ update();
+ }
+
+ VpnProfile getProfile() {
+ return mProfile;
+ }
+
+ void update(VpnProfile profile) {
+ mProfile = profile;
+ update();
+ }
+
+ void update(int state) {
+ mState = state;
+ update();
+ }
+
+ void update() {
+ if (mState < 0) {
+ String[] types = getContext().getResources()
+ .getStringArray(R.array.vpn_types_long);
+ setSummary(types[mProfile.type]);
+ } else {
+ String[] states = getContext().getResources()
+ .getStringArray(R.array.vpn_states);
+ setSummary(states[mState]);
+ }
+ setTitle(mProfile.name);
+ notifyHierarchyChanged();
+ }
+
+ @Override
+ public int compareTo(Preference preference) {
+ int result = -1;
+ if (preference instanceof VpnPreference) {
+ VpnPreference another = (VpnPreference) preference;
+ if ((result = another.mState - mState) == 0 &&
+ (result = mProfile.name.compareTo(another.mProfile.name)) == 0 &&
+ (result = mProfile.type - another.mProfile.type) == 0) {
+ result = mProfile.key.compareTo(another.mProfile.key);
+ }
+ }
+ return result;
+ }
+ }
+}
diff --git a/src/com/android/settings/widget/ChartAxis.java b/src/com/android/settings/widget/ChartAxis.java
new file mode 100644
index 0000000..4e0da1d
--- /dev/null
+++ b/src/com/android/settings/widget/ChartAxis.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.res.Resources;
+import android.text.SpannableStringBuilder;
+
+/**
+ * Axis along a {@link ChartView} that knows how to convert between raw point
+ * and screen coordinate systems.
+ */
+public interface ChartAxis {
+
+ /** Set range of raw values this axis should cover. */
+ public void setBounds(long min, long max);
+ /** Set range of screen points this axis should cover. */
+ public void setSize(float size);
+
+ /** Convert raw value into screen point. */
+ public float convertToPoint(long value);
+ /** Convert screen point into raw value. */
+ public long convertToValue(float point);
+
+ /** Build label that describes given raw value. */
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value);
+
+ /** Return list of tick points for drawing a grid. */
+ public float[] getTickPoints();
+
+ /**
+ * Test if given raw value should cause the axis to grow or shrink;
+ * returning positive value to grow and negative to shrink.
+ */
+ public int shouldAdjustAxis(long value);
+
+}
diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java
new file mode 100644
index 0000000..c2702e4
--- /dev/null
+++ b/src/com/android/settings/widget/ChartGridView.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+
+import com.android.settings.DataUsageSummary;
+import com.android.settings.R;
+import com.google.common.base.Preconditions;
+
+/**
+ * Background of {@link ChartView} that renders grid lines as requested by
+ * {@link ChartAxis#getTickPoints()}.
+ */
+public class ChartGridView extends View {
+
+ private ChartAxis mHoriz;
+ private ChartAxis mVert;
+
+ private Drawable mPrimary;
+ private Drawable mSecondary;
+ private Drawable mBorder;
+ private int mLabelColor;
+
+ private Layout mLayoutStart;
+ private Layout mLayoutEnd;
+
+ public ChartGridView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ChartGridView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ChartGridView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setWillNotDraw(false);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.ChartGridView, defStyle, 0);
+
+ mPrimary = a.getDrawable(R.styleable.ChartGridView_primaryDrawable);
+ mSecondary = a.getDrawable(R.styleable.ChartGridView_secondaryDrawable);
+ mBorder = a.getDrawable(R.styleable.ChartGridView_borderDrawable);
+ mLabelColor = a.getColor(R.styleable.ChartGridView_labelColor, Color.RED);
+
+ a.recycle();
+ }
+
+ void init(ChartAxis horiz, ChartAxis vert) {
+ mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
+ mVert = Preconditions.checkNotNull(vert, "missing vert");
+ }
+
+ void setBounds(long start, long end) {
+ final Context context = getContext();
+ mLayoutStart = makeLayout(DataUsageSummary.formatDateRange(context, start, start, true));
+ mLayoutEnd = makeLayout(DataUsageSummary.formatDateRange(context, end, end, true));
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int width = getWidth();
+ final int height = getHeight();
+
+ final Drawable secondary = mSecondary;
+ final int secondaryHeight = mSecondary.getIntrinsicHeight();
+
+ final float[] vertTicks = mVert.getTickPoints();
+ for (float y : vertTicks) {
+ final int bottom = (int) Math.min(y + secondaryHeight, height);
+ secondary.setBounds(0, (int) y, width, bottom);
+ secondary.draw(canvas);
+ }
+
+ final Drawable primary = mPrimary;
+ final int primaryWidth = mPrimary.getIntrinsicWidth();
+ final int primaryHeight = mPrimary.getIntrinsicHeight();
+
+ final float[] horizTicks = mHoriz.getTickPoints();
+ for (float x : horizTicks) {
+ final int right = (int) Math.min(x + primaryWidth, width);
+ primary.setBounds((int) x, 0, right, height);
+ primary.draw(canvas);
+ }
+
+ mBorder.setBounds(0, 0, width, height);
+ mBorder.draw(canvas);
+
+ final int padding = mLayoutStart.getHeight() / 8;
+
+ final Layout start = mLayoutStart;
+ if (start != null) {
+ canvas.save();
+ canvas.translate(0, height + padding);
+ start.draw(canvas);
+ canvas.restore();
+ }
+
+ final Layout end = mLayoutEnd;
+ if (end != null) {
+ canvas.save();
+ canvas.translate(width - end.getWidth(), height + padding);
+ end.draw(canvas);
+ canvas.restore();
+ }
+ }
+
+ private Layout makeLayout(CharSequence text) {
+ final Resources res = getResources();
+ final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+ paint.density = res.getDisplayMetrics().density;
+ paint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
+ paint.setColor(mLabelColor);
+ paint.setTextSize(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, res.getDisplayMetrics()));
+
+ return new StaticLayout(text, paint,
+ (int) Math.ceil(Layout.getDesiredWidth(text, paint)),
+ Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
+ }
+
+}
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
new file mode 100644
index 0000000..481f7cc
--- /dev/null
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.net.NetworkStatsHistory;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.settings.R;
+import com.google.common.base.Preconditions;
+
+/**
+ * {@link NetworkStatsHistory} series to render inside a {@link ChartView},
+ * using {@link ChartAxis} to map into screen coordinates.
+ */
+public class ChartNetworkSeriesView extends View {
+ private static final String TAG = "ChartNetworkSeriesView";
+ private static final boolean LOGD = false;
+
+ private ChartAxis mHoriz;
+ private ChartAxis mVert;
+
+ private Paint mPaintStroke;
+ private Paint mPaintFill;
+ private Paint mPaintFillSecondary;
+ private Paint mPaintEstimate;
+
+ private NetworkStatsHistory mStats;
+
+ private Path mPathStroke;
+ private Path mPathFill;
+ private Path mPathEstimate;
+
+ private long mPrimaryLeft;
+ private long mPrimaryRight;
+
+ /** Series will be extended to reach this end time. */
+ private long mEndTime = Long.MIN_VALUE;
+
+ private boolean mEstimateVisible = false;
+
+ private long mMax;
+ private long mMaxEstimate;
+
+ public ChartNetworkSeriesView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ChartNetworkSeriesView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ChartNetworkSeriesView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.ChartNetworkSeriesView, defStyle, 0);
+
+ final int stroke = a.getColor(R.styleable.ChartNetworkSeriesView_strokeColor, Color.RED);
+ final int fill = a.getColor(R.styleable.ChartNetworkSeriesView_fillColor, Color.RED);
+ final int fillSecondary = a.getColor(
+ R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED);
+
+ setChartColor(stroke, fill, fillSecondary);
+ setWillNotDraw(false);
+
+ a.recycle();
+
+ mPathStroke = new Path();
+ mPathFill = new Path();
+ mPathEstimate = new Path();
+ }
+
+ void init(ChartAxis horiz, ChartAxis vert) {
+ mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
+ mVert = Preconditions.checkNotNull(vert, "missing vert");
+ }
+
+ public void setChartColor(int stroke, int fill, int fillSecondary) {
+ mPaintStroke = new Paint();
+ mPaintStroke.setStrokeWidth(6.0f);
+ mPaintStroke.setColor(stroke);
+ mPaintStroke.setStyle(Style.STROKE);
+ mPaintStroke.setAntiAlias(true);
+
+ mPaintFill = new Paint();
+ mPaintFill.setColor(fill);
+ mPaintFill.setStyle(Style.FILL);
+ mPaintFill.setAntiAlias(true);
+
+ mPaintFillSecondary = new Paint();
+ mPaintFillSecondary.setColor(fillSecondary);
+ mPaintFillSecondary.setStyle(Style.FILL);
+ mPaintFillSecondary.setAntiAlias(true);
+
+ mPaintEstimate = new Paint();
+ mPaintEstimate.setStrokeWidth(3.0f);
+ mPaintEstimate.setColor(fillSecondary);
+ mPaintEstimate.setStyle(Style.STROKE);
+ mPaintEstimate.setAntiAlias(true);
+ mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1));
+ }
+
+ public void bindNetworkStats(NetworkStatsHistory stats) {
+ mStats = stats;
+
+ mPathStroke.reset();
+ mPathFill.reset();
+ mPathEstimate.reset();
+ invalidate();
+ }
+
+ /**
+ * Set the range to paint with {@link #mPaintFill}, leaving the remaining
+ * area to be painted with {@link #mPaintFillSecondary}.
+ */
+ public void setPrimaryRange(long left, long right) {
+ mPrimaryLeft = left;
+ mPrimaryRight = right;
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ generatePath();
+ }
+
+ /**
+ * Erase any existing {@link Path} and generate series outline based on
+ * currently bound {@link NetworkStatsHistory} data.
+ */
+ public void generatePath() {
+ if (LOGD) Log.d(TAG, "generatePath()");
+
+ mMax = 0;
+ mPathStroke.reset();
+ mPathFill.reset();
+ mPathEstimate.reset();
+
+ // bail when not enough stats to render
+ if (mStats == null || mStats.size() < 2) return;
+
+ final int width = getWidth();
+ final int height = getHeight();
+
+ boolean started = false;
+ float firstX = 0;
+ float lastX = 0;
+ float lastY = 0;
+ long lastTime = Long.MIN_VALUE;
+
+ // TODO: count fractional data from first bucket crossing start;
+ // currently it only accepts first full bucket.
+
+ long totalData = 0;
+
+ NetworkStatsHistory.Entry entry = null;
+ for (int i = 0; i < mStats.size(); i++) {
+ entry = mStats.getValues(i, entry);
+
+ lastTime = entry.bucketStart + entry.bucketDuration;
+ final float x = mHoriz.convertToPoint(lastTime);
+ final float y = mVert.convertToPoint(totalData);
+
+ // skip until we find first stats on screen
+ if (i > 0 && !started && x > 0) {
+ mPathStroke.moveTo(lastX, lastY);
+ mPathFill.moveTo(lastX, lastY);
+ started = true;
+ firstX = x;
+ }
+
+ if (started) {
+ mPathStroke.lineTo(x, y);
+ mPathFill.lineTo(x, y);
+ totalData += entry.rxBytes + entry.txBytes;
+ }
+
+ // skip if beyond view
+ if (x > width) break;
+
+ lastX = x;
+ lastY = y;
+ }
+
+ // when data falls short, extend to requested end time
+ if (lastTime < mEndTime) {
+ lastX = mHoriz.convertToPoint(mEndTime);
+
+ if (started) {
+ mPathStroke.lineTo(lastX, lastY);
+ mPathFill.lineTo(lastX, lastY);
+ }
+ }
+
+ if (LOGD) {
+ final RectF bounds = new RectF();
+ mPathFill.computeBounds(bounds, true);
+ Log.d(TAG, "onLayout() rendered with bounds=" + bounds.toString() + " and totalData="
+ + totalData);
+ }
+
+ // drop to bottom of graph from current location
+ mPathFill.lineTo(lastX, height);
+ mPathFill.lineTo(firstX, height);
+
+ mMax = totalData;
+
+ // build estimated data
+ mPathEstimate.moveTo(lastX, lastY);
+
+ final long now = System.currentTimeMillis();
+ final long bucketDuration = mStats.getBucketDuration();
+
+ // long window is average over two weeks
+ entry = mStats.getValues(lastTime - WEEK_IN_MILLIS * 2, lastTime, now, entry);
+ final long longWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
+ / entry.bucketDuration;
+
+ long futureTime = 0;
+ while (lastX < width) {
+ futureTime += bucketDuration;
+
+ // short window is day average last week
+ final long lastWeekTime = lastTime - WEEK_IN_MILLIS + (futureTime % WEEK_IN_MILLIS);
+ entry = mStats.getValues(lastWeekTime - DAY_IN_MILLIS, lastWeekTime, now, entry);
+ final long shortWindow = (entry.rxBytes + entry.txBytes) * bucketDuration
+ / entry.bucketDuration;
+
+ totalData += (longWindow * 7 + shortWindow * 3) / 10;
+
+ lastX = mHoriz.convertToPoint(lastTime + futureTime);
+ lastY = mVert.convertToPoint(totalData);
+
+ mPathEstimate.lineTo(lastX, lastY);
+ }
+
+ mMaxEstimate = totalData;
+ }
+
+ public void setEndTime(long endTime) {
+ mEndTime = endTime;
+ }
+
+ public void setEstimateVisible(boolean estimateVisible) {
+ mEstimateVisible = estimateVisible;
+ invalidate();
+ }
+
+ public long getMaxEstimate() {
+ return mMaxEstimate;
+ }
+
+ public long getMaxVisible() {
+ return mEstimateVisible ? mMaxEstimate : mMax;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int save;
+
+ final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
+ final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
+
+ if (mEstimateVisible) {
+ save = canvas.save();
+ canvas.clipRect(0, 0, getWidth(), getHeight());
+ canvas.drawPath(mPathEstimate, mPaintEstimate);
+ canvas.restoreToCount(save);
+ }
+
+ save = canvas.save();
+ canvas.clipRect(0, 0, primaryLeftPoint, getHeight());
+ canvas.drawPath(mPathFill, mPaintFillSecondary);
+ canvas.restoreToCount(save);
+
+ save = canvas.save();
+ canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight());
+ canvas.drawPath(mPathFill, mPaintFillSecondary);
+ canvas.restoreToCount(save);
+
+ save = canvas.save();
+ canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight());
+ canvas.drawPath(mPathFill, mPaintFill);
+ canvas.drawPath(mPathStroke, mPaintStroke);
+ canvas.restoreToCount(save);
+
+ }
+}
diff --git a/src/com/android/settings/widget/ChartSweepView.java b/src/com/android/settings/widget/ChartSweepView.java
new file mode 100644
index 0000000..d5e8de8
--- /dev/null
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.DynamicLayout;
+import android.text.Layout.Alignment;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.settings.R;
+import com.google.common.base.Preconditions;
+
+/**
+ * Sweep across a {@link ChartView} at a specific {@link ChartAxis} value, which
+ * a user can drag.
+ */
+public class ChartSweepView extends FrameLayout {
+
+ private Drawable mSweep;
+ private Rect mSweepPadding = new Rect();
+ private Point mSweepOffset = new Point();
+
+ private Rect mMargins = new Rect();
+ private float mNeighborMargin;
+
+ private int mFollowAxis;
+
+ private int mLabelSize;
+ private int mLabelTemplateRes;
+ private int mLabelColor;
+
+ private SpannableStringBuilder mLabelTemplate;
+ private DynamicLayout mLabelLayout;
+
+ private ChartAxis mAxis;
+ private long mValue;
+
+ private long mValidAfter;
+ private long mValidBefore;
+ private ChartSweepView mValidAfterDynamic;
+ private ChartSweepView mValidBeforeDynamic;
+
+ public static final int HORIZONTAL = 0;
+ public static final int VERTICAL = 1;
+
+ public interface OnSweepListener {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone);
+ }
+
+ private OnSweepListener mListener;
+ private MotionEvent mTracking;
+
+ public ChartSweepView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ChartSweepView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ChartSweepView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.ChartSweepView, defStyle, 0);
+
+ setSweepDrawable(a.getDrawable(R.styleable.ChartSweepView_sweepDrawable));
+ setFollowAxis(a.getInt(R.styleable.ChartSweepView_followAxis, -1));
+ setNeighborMargin(a.getDimensionPixelSize(R.styleable.ChartSweepView_neighborMargin, 0));
+
+ setLabelSize(a.getDimensionPixelSize(R.styleable.ChartSweepView_labelSize, 0));
+ setLabelTemplate(a.getResourceId(R.styleable.ChartSweepView_labelTemplate, 0));
+ setLabelColor(a.getColor(R.styleable.ChartSweepView_labelColor, Color.BLUE));
+
+ a.recycle();
+
+ setClipToPadding(false);
+ setClipChildren(false);
+ setWillNotDraw(false);
+ }
+
+ void init(ChartAxis axis) {
+ mAxis = Preconditions.checkNotNull(axis, "missing axis");
+ }
+
+ public int getFollowAxis() {
+ return mFollowAxis;
+ }
+
+ public Rect getMargins() {
+ return mMargins;
+ }
+
+ /**
+ * Return the number of pixels that the "target" area is inset from the
+ * {@link View} edge, along the current {@link #setFollowAxis(int)}.
+ */
+ private float getTargetInset() {
+ if (mFollowAxis == VERTICAL) {
+ final float targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
+ - mSweepPadding.bottom;
+ return mSweepPadding.top + (targetHeight / 2);
+ } else {
+ final float targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
+ - mSweepPadding.right;
+ return mSweepPadding.left + (targetWidth / 2);
+ }
+ }
+
+ public void addOnSweepListener(OnSweepListener listener) {
+ mListener = listener;
+ }
+
+ private void dispatchOnSweep(boolean sweepDone) {
+ if (mListener != null) {
+ mListener.onSweep(this, sweepDone);
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ requestLayout();
+ }
+
+ public void setSweepDrawable(Drawable sweep) {
+ if (mSweep != null) {
+ mSweep.setCallback(null);
+ unscheduleDrawable(mSweep);
+ }
+
+ if (sweep != null) {
+ sweep.setCallback(this);
+ if (sweep.isStateful()) {
+ sweep.setState(getDrawableState());
+ }
+ sweep.setVisible(getVisibility() == VISIBLE, false);
+ mSweep = sweep;
+ sweep.getPadding(mSweepPadding);
+ } else {
+ mSweep = null;
+ }
+
+ invalidate();
+ }
+
+ public void setFollowAxis(int followAxis) {
+ mFollowAxis = followAxis;
+ }
+
+ public void setLabelSize(int size) {
+ mLabelSize = size;
+ invalidateLabelTemplate();
+ }
+
+ public void setLabelTemplate(int resId) {
+ mLabelTemplateRes = resId;
+ invalidateLabelTemplate();
+ }
+
+ public void setLabelColor(int color) {
+ mLabelColor = color;
+ invalidateLabelTemplate();
+ }
+
+ private void invalidateLabelTemplate() {
+ if (mLabelTemplateRes != 0) {
+ final CharSequence template = getResources().getText(mLabelTemplateRes);
+
+ final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+ paint.density = getResources().getDisplayMetrics().density;
+ paint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
+ paint.setColor(mLabelColor);
+
+ mLabelTemplate = new SpannableStringBuilder(template);
+ mLabelLayout = new DynamicLayout(
+ mLabelTemplate, paint, mLabelSize, Alignment.ALIGN_RIGHT, 1f, 0f, false);
+ invalidateLabel();
+
+ } else {
+ mLabelTemplate = null;
+ mLabelLayout = null;
+ }
+
+ invalidate();
+ requestLayout();
+ }
+
+ private void invalidateLabel() {
+ if (mLabelTemplate != null && mAxis != null) {
+ mAxis.buildLabel(getResources(), mLabelTemplate, mValue);
+ invalidate();
+ }
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ if (mSweep != null) {
+ mSweep.jumpToCurrentState();
+ }
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (mSweep != null) {
+ mSweep.setVisible(visibility == VISIBLE, false);
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return who == mSweep || super.verifyDrawable(who);
+ }
+
+ public ChartAxis getAxis() {
+ return mAxis;
+ }
+
+ public void setValue(long value) {
+ mValue = value;
+ invalidateLabel();
+ }
+
+ public long getValue() {
+ return mValue;
+ }
+
+ public float getPoint() {
+ if (isEnabled()) {
+ return mAxis.convertToPoint(mValue);
+ } else {
+ // when disabled, show along top edge
+ return 0;
+ }
+ }
+
+ /**
+ * Set valid range this sweep can move within, in {@link #mAxis} values. The
+ * most restrictive combination of all valid ranges is used.
+ */
+ public void setValidRange(long validAfter, long validBefore) {
+ mValidAfter = validAfter;
+ mValidBefore = validBefore;
+ }
+
+ public void setNeighborMargin(float neighborMargin) {
+ mNeighborMargin = neighborMargin;
+ }
+
+ /**
+ * Set valid range this sweep can move within, defined by the given
+ * {@link ChartSweepView}. The most restrictive combination of all valid
+ * ranges is used.
+ */
+ public void setValidRangeDynamic(ChartSweepView validAfter, ChartSweepView validBefore) {
+ mValidAfterDynamic = validAfter;
+ mValidBeforeDynamic = validBefore;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!isEnabled()) return false;
+
+ final View parent = (View) getParent();
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+
+ // only start tracking when in sweet spot
+ final boolean accept;
+ if (mFollowAxis == VERTICAL) {
+ accept = event.getX() > getWidth() - (mSweepPadding.right * 2);
+ } else {
+ accept = event.getY() > getHeight() - (mSweepPadding.bottom * 2);
+ }
+
+ if (accept) {
+ mTracking = event.copy();
+
+ // starting drag should activate entire chart
+ if (!parent.isActivated()) {
+ parent.setActivated(true);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+ case MotionEvent.ACTION_MOVE: {
+ getParent().requestDisallowInterceptTouchEvent(true);
+
+ // content area of parent
+ final Rect parentContent = getParentContentRect();
+ final Rect clampRect = computeClampRect(parentContent);
+
+ if (mFollowAxis == VERTICAL) {
+ final float currentTargetY = getTop() - mMargins.top;
+ final float requestedTargetY = currentTargetY
+ + (event.getRawY() - mTracking.getRawY());
+ final float clampedTargetY = MathUtils.constrain(
+ requestedTargetY, clampRect.top, clampRect.bottom);
+ setTranslationY(clampedTargetY - currentTargetY);
+
+ setValue(mAxis.convertToValue(clampedTargetY - parentContent.top));
+ } else {
+ final float currentTargetX = getLeft() - mMargins.left;
+ final float requestedTargetX = currentTargetX
+ + (event.getRawX() - mTracking.getRawX());
+ final float clampedTargetX = MathUtils.constrain(
+ requestedTargetX, clampRect.left, clampRect.right);
+ setTranslationX(clampedTargetX - currentTargetX);
+
+ setValue(mAxis.convertToValue(clampedTargetX - parentContent.left));
+ }
+
+ dispatchOnSweep(false);
+ return true;
+ }
+ case MotionEvent.ACTION_UP: {
+ mTracking = null;
+ dispatchOnSweep(true);
+ setTranslationX(0);
+ setTranslationY(0);
+ requestLayout();
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Update {@link #mValue} based on current position, including any
+ * {@link #onTouchEvent(MotionEvent)} in progress. Typically used when
+ * {@link ChartAxis} changes during sweep adjustment.
+ */
+ public void updateValueFromPosition() {
+ final Rect parentContent = getParentContentRect();
+ if (mFollowAxis == VERTICAL) {
+ final float effectiveY = getY() - mMargins.top - parentContent.top;
+ setValue(mAxis.convertToValue(effectiveY));
+ } else {
+ final float effectiveX = getX() - mMargins.left - parentContent.left;
+ setValue(mAxis.convertToValue(effectiveX));
+ }
+ }
+
+ public int shouldAdjustAxis() {
+ return mAxis.shouldAdjustAxis(getValue());
+ }
+
+ private Rect getParentContentRect() {
+ final View parent = (View) getParent();
+ return new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
+ parent.getWidth() - parent.getPaddingRight(),
+ parent.getHeight() - parent.getPaddingBottom());
+ }
+
+ @Override
+ public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
+ // ignored to keep LayoutTransition from animating us
+ }
+
+ @Override
+ public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
+ // ignored to keep LayoutTransition from animating us
+ }
+
+ private long getValidAfterDynamic() {
+ final ChartSweepView dynamic = mValidAfterDynamic;
+ return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MIN_VALUE;
+ }
+
+ private long getValidBeforeDynamic() {
+ final ChartSweepView dynamic = mValidBeforeDynamic;
+ return dynamic != null && dynamic.isEnabled() ? dynamic.getValue() : Long.MAX_VALUE;
+ }
+
+ /**
+ * Compute {@link Rect} in {@link #getParent()} coordinates that we should
+ * be clamped inside of, usually from {@link #setValidRange(long, long)}
+ * style rules.
+ */
+ private Rect computeClampRect(Rect parentContent) {
+ // create two rectangles, and pick most restrictive combination
+ final Rect rect = buildClampRect(parentContent, mValidAfter, mValidBefore, 0f);
+ final Rect dynamicRect = buildClampRect(
+ parentContent, getValidAfterDynamic(), getValidBeforeDynamic(), mNeighborMargin);
+
+ rect.intersect(dynamicRect);
+ return rect;
+ }
+
+ private Rect buildClampRect(
+ Rect parentContent, long afterValue, long beforeValue, float margin) {
+ if (mAxis instanceof InvertedChartAxis) {
+ long temp = beforeValue;
+ beforeValue = afterValue;
+ afterValue = temp;
+ }
+
+ final boolean afterValid = afterValue != Long.MIN_VALUE && afterValue != Long.MAX_VALUE;
+ final boolean beforeValid = beforeValue != Long.MIN_VALUE && beforeValue != Long.MAX_VALUE;
+
+ final float afterPoint = mAxis.convertToPoint(afterValue) + margin;
+ final float beforePoint = mAxis.convertToPoint(beforeValue) - margin;
+
+ final Rect clampRect = new Rect(parentContent);
+ if (mFollowAxis == VERTICAL) {
+ if (beforeValid) clampRect.bottom = clampRect.top + (int) beforePoint;
+ if (afterValid) clampRect.top += afterPoint;
+ } else {
+ if (beforeValid) clampRect.right = clampRect.left + (int) beforePoint;
+ if (afterValid) clampRect.left += afterPoint;
+ }
+ return clampRect;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ if (mSweep.isStateful()) {
+ mSweep.setState(getDrawableState());
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ // TODO: handle vertical labels
+ if (isEnabled() && mLabelLayout != null) {
+ final int sweepHeight = mSweep.getIntrinsicHeight();
+ final int templateHeight = mLabelLayout.getHeight();
+
+ mSweepOffset.x = 0;
+ mSweepOffset.y = (int) ((templateHeight / 2) - getTargetInset());
+ setMeasuredDimension(mSweep.getIntrinsicWidth(), Math.max(sweepHeight, templateHeight));
+
+ } else {
+ mSweepOffset.x = 0;
+ mSweepOffset.y = 0;
+ setMeasuredDimension(mSweep.getIntrinsicWidth(), mSweep.getIntrinsicHeight());
+ }
+
+ if (mFollowAxis == VERTICAL) {
+ final int targetHeight = mSweep.getIntrinsicHeight() - mSweepPadding.top
+ - mSweepPadding.bottom;
+ mMargins.top = -(mSweepPadding.top + (targetHeight / 2));
+ mMargins.bottom = 0;
+ mMargins.left = -mSweepPadding.left;
+ mMargins.right = mSweepPadding.right;
+ } else {
+ final int targetWidth = mSweep.getIntrinsicWidth() - mSweepPadding.left
+ - mSweepPadding.right;
+ mMargins.left = -(mSweepPadding.left + (targetWidth / 2));
+ mMargins.right = 0;
+ mMargins.top = -mSweepPadding.top;
+ mMargins.bottom = mSweepPadding.bottom;
+ }
+
+ mMargins.offset(-mSweepOffset.x, -mSweepOffset.y);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int width = getWidth();
+ final int height = getHeight();
+
+ final int labelSize;
+ if (isEnabled() && mLabelLayout != null) {
+ mLabelLayout.draw(canvas);
+ labelSize = mLabelSize;
+ } else {
+ labelSize = 0;
+ }
+
+ if (mFollowAxis == VERTICAL) {
+ mSweep.setBounds(labelSize, mSweepOffset.y, width,
+ mSweepOffset.y + mSweep.getIntrinsicHeight());
+ } else {
+ mSweep.setBounds(mSweepOffset.x, labelSize,
+ mSweepOffset.x + mSweep.getIntrinsicWidth(), height);
+ }
+
+ mSweep.draw(canvas);
+ }
+
+}
diff --git a/src/com/android/settings/widget/ChartView.java b/src/com/android/settings/widget/ChartView.java
new file mode 100644
index 0000000..a5b8b09
--- /dev/null
+++ b/src/com/android/settings/widget/ChartView.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Container for two-dimensional chart, drawn with a combination of
+ * {@link ChartGridView}, {@link ChartNetworkSeriesView} and {@link ChartSweepView}
+ * children. The entire chart uses {@link ChartAxis} to map between raw values
+ * and screen coordinates.
+ */
+public class ChartView extends FrameLayout {
+ private static final String TAG = "ChartView";
+
+ // TODO: extend something that supports two-dimensional scrolling
+
+ private static final int SWEEP_GRAVITY = Gravity.TOP | Gravity.LEFT;
+
+ ChartAxis mHoriz;
+ ChartAxis mVert;
+
+ private Rect mContent = new Rect();
+
+ public ChartView(Context context) {
+ this(context, null, 0);
+ }
+
+ public ChartView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ChartView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setClipToPadding(false);
+ setClipChildren(false);
+ }
+
+ void init(ChartAxis horiz, ChartAxis vert) {
+ mHoriz = checkNotNull(horiz, "missing horiz");
+ mVert = checkNotNull(vert, "missing vert");
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mContent.set(getPaddingLeft(), getPaddingTop(), r - l - getPaddingRight(),
+ b - t - getPaddingBottom());
+ final int width = mContent.width();
+ final int height = mContent.height();
+
+ // no scrolling yet, so tell dimensions to fill exactly
+ mHoriz.setSize(width);
+ mVert.setSize(height);
+
+ final Rect parentRect = new Rect();
+ final Rect childRect = new Rect();
+
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ final LayoutParams params = (LayoutParams) child.getLayoutParams();
+
+ parentRect.set(mContent);
+
+ if (child instanceof ChartNetworkSeriesView || child instanceof ChartGridView) {
+ // series are always laid out to fill entire graph area
+ // TODO: handle scrolling for series larger than content area
+ Gravity.apply(params.gravity, width, height, parentRect, childRect);
+ child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+
+ } else if (child instanceof ChartSweepView) {
+ // sweep is always placed along specific dimension
+ final ChartSweepView sweep = (ChartSweepView) child;
+ final Rect sweepMargins = sweep.getMargins();
+
+ if (sweep.getFollowAxis() == ChartSweepView.VERTICAL) {
+ parentRect.top += sweepMargins.top + (int) sweep.getPoint();
+ parentRect.bottom = parentRect.top;
+ parentRect.left += sweepMargins.left;
+ parentRect.right += sweepMargins.right;
+ Gravity.apply(SWEEP_GRAVITY, parentRect.width(), child.getMeasuredHeight(),
+ parentRect, childRect);
+
+ } else {
+ parentRect.left += sweepMargins.left + (int) sweep.getPoint();
+ parentRect.right = parentRect.left;
+ parentRect.top += sweepMargins.top;
+ parentRect.bottom += sweepMargins.bottom;
+ Gravity.apply(SWEEP_GRAVITY, child.getMeasuredWidth(), parentRect.height(),
+ parentRect, childRect);
+ }
+ }
+
+ child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+ }
+ }
+
+}
diff --git a/src/com/android/settings/widget/DataUsageChartView.java b/src/com/android/settings/widget/DataUsageChartView.java
new file mode 100644
index 0000000..a1c92e1
--- /dev/null
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.NetworkPolicy;
+import android.net.NetworkStatsHistory;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.settings.R;
+import com.android.settings.widget.ChartSweepView.OnSweepListener;
+
+/**
+ * Specific {@link ChartView} that displays {@link ChartNetworkSeriesView} along
+ * with {@link ChartSweepView} for inspection ranges and warning/limits.
+ */
+public class DataUsageChartView extends ChartView {
+
+ private static final long KB_IN_BYTES = 1024;
+ private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+ private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+
+ private static final int MSG_UPDATE_AXIS = 100;
+ private static final long DELAY_MILLIS = 250;
+
+ private ChartGridView mGrid;
+ private ChartNetworkSeriesView mSeries;
+ private ChartNetworkSeriesView mDetailSeries;
+
+ private NetworkStatsHistory mHistory;
+
+ private ChartSweepView mSweepLeft;
+ private ChartSweepView mSweepRight;
+ private ChartSweepView mSweepWarning;
+ private ChartSweepView mSweepLimit;
+
+ private Handler mHandler;
+
+ /** Current maximum value of {@link #mVert}. */
+ private long mVertMax;
+
+ public interface DataUsageChartListener {
+ public void onInspectRangeChanged();
+ public void onWarningChanged();
+ public void onLimitChanged();
+ }
+
+ private DataUsageChartListener mListener;
+
+ public DataUsageChartView(Context context) {
+ this(context, null, 0);
+ }
+
+ public DataUsageChartView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DataUsageChartView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(new TimeAxis(), new InvertedChartAxis(new DataAxis()));
+
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ final ChartSweepView sweep = (ChartSweepView) msg.obj;
+ updateVertAxisBounds(sweep);
+ updateEstimateVisible();
+
+ // we keep dispatching repeating updates until sweep is dropped
+ sendUpdateAxisDelayed(sweep, true);
+ }
+ };
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mGrid = (ChartGridView) findViewById(R.id.grid);
+ mSeries = (ChartNetworkSeriesView) findViewById(R.id.series);
+ mDetailSeries = (ChartNetworkSeriesView) findViewById(R.id.detail_series);
+ mDetailSeries.setVisibility(View.GONE);
+
+ mSweepLeft = (ChartSweepView) findViewById(R.id.sweep_left);
+ mSweepRight = (ChartSweepView) findViewById(R.id.sweep_right);
+ mSweepLimit = (ChartSweepView) findViewById(R.id.sweep_limit);
+ mSweepWarning = (ChartSweepView) findViewById(R.id.sweep_warning);
+
+ // prevent sweeps from crossing each other
+ mSweepLeft.setValidRangeDynamic(null, mSweepRight);
+ mSweepRight.setValidRangeDynamic(mSweepLeft, null);
+ mSweepWarning.setValidRangeDynamic(null, mSweepLimit);
+ mSweepLimit.setValidRangeDynamic(mSweepWarning, null);
+
+ mSweepLeft.addOnSweepListener(mHorizListener);
+ mSweepRight.addOnSweepListener(mHorizListener);
+ mSweepWarning.addOnSweepListener(mVertListener);
+ mSweepLimit.addOnSweepListener(mVertListener);
+
+ // tell everyone about our axis
+ mGrid.init(mHoriz, mVert);
+ mSeries.init(mHoriz, mVert);
+ mDetailSeries.init(mHoriz, mVert);
+ mSweepLeft.init(mHoriz);
+ mSweepRight.init(mHoriz);
+ mSweepWarning.init(mVert);
+ mSweepLimit.init(mVert);
+
+ setActivated(false);
+ }
+
+ public void setListener(DataUsageChartListener listener) {
+ mListener = listener;
+ }
+
+ public void bindNetworkStats(NetworkStatsHistory stats) {
+ mSeries.bindNetworkStats(stats);
+ mHistory = stats;
+ updateVertAxisBounds(null);
+ updateEstimateVisible();
+ updatePrimaryRange();
+ requestLayout();
+ }
+
+ public void bindDetailNetworkStats(NetworkStatsHistory stats) {
+ mDetailSeries.bindNetworkStats(stats);
+ mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
+ if (mHistory != null) {
+ mDetailSeries.setEndTime(mHistory.getEnd());
+ }
+ updateVertAxisBounds(null);
+ updateEstimateVisible();
+ updatePrimaryRange();
+ requestLayout();
+ }
+
+ public void bindNetworkPolicy(NetworkPolicy policy) {
+ if (policy == null) {
+ mSweepLimit.setVisibility(View.INVISIBLE);
+ mSweepLimit.setValue(-1);
+ mSweepWarning.setVisibility(View.INVISIBLE);
+ mSweepWarning.setValue(-1);
+ return;
+ }
+
+ if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
+ mSweepLimit.setVisibility(View.VISIBLE);
+ mSweepLimit.setEnabled(true);
+ mSweepLimit.setValue(policy.limitBytes);
+ } else {
+ mSweepLimit.setVisibility(View.VISIBLE);
+ mSweepLimit.setEnabled(false);
+ mSweepLimit.setValue(-1);
+ }
+
+ if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
+ mSweepWarning.setVisibility(View.VISIBLE);
+ mSweepWarning.setValue(policy.warningBytes);
+ } else {
+ mSweepWarning.setVisibility(View.INVISIBLE);
+ mSweepWarning.setValue(-1);
+ }
+
+ updateVertAxisBounds(null);
+ requestLayout();
+ }
+
+ /**
+ * Update {@link #mVert} to both show data from {@link NetworkStatsHistory}
+ * and controls from {@link NetworkPolicy}.
+ */
+ private void updateVertAxisBounds(ChartSweepView activeSweep) {
+ final long max = mVertMax;
+ final long newMax;
+ if (activeSweep != null) {
+ final int adjustAxis = activeSweep.shouldAdjustAxis();
+ if (adjustAxis > 0) {
+ // hovering around upper edge, grow axis
+ newMax = max * 11 / 10;
+ } else if (adjustAxis < 0) {
+ // hovering around lower edge, shrink axis
+ newMax = max * 9 / 10;
+ } else {
+ newMax = max;
+ }
+
+ } else {
+ // try showing all known data and policy
+ final long maxSweep = Math.max(mSweepWarning.getValue(), mSweepLimit.getValue());
+ final long maxVisible = Math.max(mSeries.getMaxVisible(), maxSweep) * 12 / 10;
+ newMax = Math.max(maxVisible, 2 * GB_IN_BYTES);
+ }
+
+ // only invalidate when vertMax actually changed
+ if (newMax != mVertMax) {
+ mVertMax = newMax;
+
+ mVert.setBounds(0L, newMax);
+ mSweepWarning.setValidRange(0L, newMax);
+ mSweepLimit.setValidRange(0L, newMax);
+
+ mSeries.generatePath();
+ mDetailSeries.generatePath();
+
+ mGrid.invalidate();
+ mSeries.invalidate();
+ mDetailSeries.invalidate();
+
+ // since we just changed axis, make sweep recalculate its value
+ if (activeSweep != null) {
+ activeSweep.updateValueFromPosition();
+ }
+ }
+ }
+
+ /**
+ * Control {@link ChartNetworkSeriesView#setEstimateVisible(boolean)} based
+ * on how close estimate comes to {@link #mSweepWarning}.
+ */
+ private void updateEstimateVisible() {
+ final long maxEstimate = mSeries.getMaxEstimate();
+
+ // show estimate when near warning/limit
+ long interestLine = Long.MAX_VALUE;
+ if (mSweepWarning.isEnabled()) {
+ interestLine = mSweepWarning.getValue();
+ } else if (mSweepLimit.isEnabled()) {
+ interestLine = mSweepLimit.getValue();
+ }
+
+ final boolean estimateVisible = (maxEstimate >= interestLine * 7 / 10);
+ mSeries.setEstimateVisible(estimateVisible);
+ }
+
+ private OnSweepListener mHorizListener = new OnSweepListener() {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone) {
+ updatePrimaryRange();
+
+ // update detail list only when done sweeping
+ if (sweepDone && mListener != null) {
+ mListener.onInspectRangeChanged();
+ }
+ }
+ };
+
+ private void sendUpdateAxisDelayed(ChartSweepView sweep, boolean force) {
+ if (force || !mHandler.hasMessages(MSG_UPDATE_AXIS, sweep)) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_UPDATE_AXIS, sweep), DELAY_MILLIS);
+ }
+ }
+
+ private void clearUpdateAxisDelayed(ChartSweepView sweep) {
+ mHandler.removeMessages(MSG_UPDATE_AXIS, sweep);
+ }
+
+ private OnSweepListener mVertListener = new OnSweepListener() {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone) {
+ if (sweepDone) {
+ clearUpdateAxisDelayed(sweep);
+ updateEstimateVisible();
+
+ if (sweep == mSweepWarning && mListener != null) {
+ mListener.onWarningChanged();
+ } else if (sweep == mSweepLimit && mListener != null) {
+ mListener.onLimitChanged();
+ }
+ } else {
+ // while moving, kick off delayed grow/shrink axis updates
+ sendUpdateAxisDelayed(sweep, false);
+ }
+ }
+ };
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isActivated()) return false;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ return true;
+ }
+ case MotionEvent.ACTION_UP: {
+ setActivated(true);
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
+
+ public long getInspectStart() {
+ return mSweepLeft.getValue();
+ }
+
+ public long getInspectEnd() {
+ return mSweepRight.getValue();
+ }
+
+ public long getWarningBytes() {
+ return mSweepWarning.getValue();
+ }
+
+ public long getLimitBytes() {
+ return mSweepLimit.getValue();
+ }
+
+ private long getStatsStart() {
+ return mHistory != null ? mHistory.getStart() : Long.MIN_VALUE;
+ }
+
+ private long getStatsEnd() {
+ return mHistory != null ? mHistory.getEnd() : Long.MAX_VALUE;
+ }
+
+ /**
+ * Set the exact time range that should be displayed, updating how
+ * {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the
+ * last "week" of available data, without triggering listener events.
+ */
+ public void setVisibleRange(long visibleStart, long visibleEnd) {
+ mHoriz.setBounds(visibleStart, visibleEnd);
+ mGrid.setBounds(visibleStart, visibleEnd);
+
+ final long validStart = Math.max(visibleStart, getStatsStart());
+ final long validEnd = Math.min(visibleEnd, getStatsEnd());
+
+ // prevent time sweeps from leaving valid data
+ mSweepLeft.setValidRange(validStart, validEnd);
+ mSweepRight.setValidRange(validStart, validEnd);
+
+ // default sweeps to last week of data
+ final long halfRange = (visibleEnd + visibleStart) / 2;
+ final long sweepMax = validEnd;
+ final long sweepMin = Math.max(visibleStart, (sweepMax - DateUtils.WEEK_IN_MILLIS));
+
+ mSweepLeft.setValue(sweepMin);
+ mSweepRight.setValue(sweepMax);
+
+ requestLayout();
+ mSeries.generatePath();
+ mSeries.invalidate();
+
+ updateVertAxisBounds(null);
+ updateEstimateVisible();
+ updatePrimaryRange();
+ }
+
+ private void updatePrimaryRange() {
+ final long left = mSweepLeft.getValue();
+ final long right = mSweepRight.getValue();
+
+ // prefer showing primary range on detail series, when available
+ if (mDetailSeries.getVisibility() == View.VISIBLE) {
+ mDetailSeries.setPrimaryRange(left, right);
+ mSeries.setPrimaryRange(0, 0);
+ } else {
+ mSeries.setPrimaryRange(left, right);
+ }
+ }
+
+ public static class TimeAxis implements ChartAxis {
+ private static final long TICK_INTERVAL = DateUtils.DAY_IN_MILLIS * 7;
+
+ private long mMin;
+ private long mMax;
+ private float mSize;
+
+ public TimeAxis() {
+ final long currentTime = System.currentTimeMillis();
+ setBounds(currentTime - DateUtils.DAY_IN_MILLIS * 30, currentTime);
+ }
+
+ /** {@inheritDoc} */
+ public void setBounds(long min, long max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ /** {@inheritDoc} */
+ public void setSize(float size) {
+ this.mSize = size;
+ }
+
+ /** {@inheritDoc} */
+ public float convertToPoint(long value) {
+ return (mSize * (value - mMin)) / (mMax - mMin);
+ }
+
+ /** {@inheritDoc} */
+ public long convertToValue(float point) {
+ return (long) (mMin + ((point * (mMax - mMin)) / mSize));
+ }
+
+ /** {@inheritDoc} */
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value) {
+ // TODO: convert to better string
+ builder.replace(0, builder.length(), Long.toString(value));
+ }
+
+ /** {@inheritDoc} */
+ public float[] getTickPoints() {
+ // tick mark for every week
+ final int tickCount = (int) ((mMax - mMin) / TICK_INTERVAL);
+ final float[] tickPoints = new float[tickCount];
+ for (int i = 0; i < tickCount; i++) {
+ tickPoints[i] = convertToPoint(mMax - (TICK_INTERVAL * i));
+ }
+ return tickPoints;
+ }
+
+ /** {@inheritDoc} */
+ public int shouldAdjustAxis(long value) {
+ // time axis never adjusts
+ return 0;
+ }
+ }
+
+ public static class DataAxis implements ChartAxis {
+ private long mMin;
+ private long mMax;
+ private float mSize;
+
+ /** {@inheritDoc} */
+ public void setBounds(long min, long max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ /** {@inheritDoc} */
+ public void setSize(float size) {
+ mSize = size;
+ }
+
+ /** {@inheritDoc} */
+ public float convertToPoint(long value) {
+ // derived polynomial fit to make lower values more visible
+ final double normalized = ((double) value - mMin) / (mMax - mMin);
+ final double fraction = Math.pow(
+ 10, 0.36884343106175121463 * Math.log10(normalized) + -0.04328199452018252624);
+ return (float) (fraction * mSize);
+ }
+
+ /** {@inheritDoc} */
+ public long convertToValue(float point) {
+ final double normalized = point / mSize;
+ final double fraction = 1.3102228476089056629
+ * Math.pow(normalized, 2.7111774693164631640);
+ return (long) (mMin + (fraction * (mMax - mMin)));
+ }
+
+ private static final Object sSpanSize = new Object();
+ private static final Object sSpanUnit = new Object();
+
+ /** {@inheritDoc} */
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value) {
+
+ float result = value;
+ final CharSequence unit;
+ if (result <= 100 * MB_IN_BYTES) {
+ unit = res.getText(com.android.internal.R.string.megabyteShort);
+ result /= MB_IN_BYTES;
+ } else {
+ unit = res.getText(com.android.internal.R.string.gigabyteShort);
+ result /= GB_IN_BYTES;
+ }
+
+ final CharSequence size;
+ if (result < 10) {
+ size = String.format("%.1f", result);
+ } else {
+ size = String.format("%.0f", result);
+ }
+
+ final int[] sizeBounds = findOrCreateSpan(builder, sSpanSize, "^1");
+ builder.replace(sizeBounds[0], sizeBounds[1], size);
+ final int[] unitBounds = findOrCreateSpan(builder, sSpanUnit, "^2");
+ builder.replace(unitBounds[0], unitBounds[1], unit);
+ }
+
+ /** {@inheritDoc} */
+ public float[] getTickPoints() {
+ final long range = mMax - mMin;
+ final long tickJump = 256 * MB_IN_BYTES;
+
+ final int tickCount = (int) (range / tickJump);
+ final float[] tickPoints = new float[tickCount];
+ long value = mMin;
+ for (int i = 0; i < tickPoints.length; i++) {
+ tickPoints[i] = convertToPoint(value);
+ value += tickJump;
+ }
+
+ return tickPoints;
+ }
+
+ /** {@inheritDoc} */
+ public int shouldAdjustAxis(long value) {
+ final float point = convertToPoint(value);
+ if (point < mSize * 0.1) {
+ return -1;
+ } else if (point > mSize * 0.85) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ private static int[] findOrCreateSpan(
+ SpannableStringBuilder builder, Object key, CharSequence bootstrap) {
+ int start = builder.getSpanStart(key);
+ int end = builder.getSpanEnd(key);
+ if (start == -1) {
+ start = TextUtils.indexOf(builder, bootstrap);
+ end = start + bootstrap.length();
+ builder.setSpan(key, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ return new int[] { start, end };
+ }
+
+}
diff --git a/src/com/android/settings/widget/InvertedChartAxis.java b/src/com/android/settings/widget/InvertedChartAxis.java
new file mode 100644
index 0000000..96aec7b
--- /dev/null
+++ b/src/com/android/settings/widget/InvertedChartAxis.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.res.Resources;
+import android.text.SpannableStringBuilder;
+
+/**
+ * Utility to invert another {@link ChartAxis}.
+ */
+public class InvertedChartAxis implements ChartAxis {
+ private final ChartAxis mWrapped;
+ private float mSize;
+
+ public InvertedChartAxis(ChartAxis wrapped) {
+ mWrapped = wrapped;
+ }
+
+ /** {@inheritDoc} */
+ public void setBounds(long min, long max) {
+ mWrapped.setBounds(min, max);
+ }
+
+ /** {@inheritDoc} */
+ public void setSize(float size) {
+ mSize = size;
+ mWrapped.setSize(size);
+ }
+
+ /** {@inheritDoc} */
+ public float convertToPoint(long value) {
+ return mSize - mWrapped.convertToPoint(value);
+ }
+
+ /** {@inheritDoc} */
+ public long convertToValue(float point) {
+ return mWrapped.convertToValue(mSize - point);
+ }
+
+ /** {@inheritDoc} */
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value) {
+ mWrapped.buildLabel(res, builder, value);
+ }
+
+ /** {@inheritDoc} */
+ public float[] getTickPoints() {
+ final float[] points = mWrapped.getTickPoints();
+ for (int i = 0; i < points.length; i++) {
+ points[i] = mSize - points[i];
+ }
+ return points;
+ }
+
+ /** {@inheritDoc} */
+ public int shouldAdjustAxis(long value) {
+ return mWrapped.shouldAdjustAxis(value);
+ }
+}
diff --git a/src/com/android/settings/wifi/AccessPoint.java b/src/com/android/settings/wifi/AccessPoint.java
index 774ac58..8181746 100644
--- a/src/com/android/settings/wifi/AccessPoint.java
+++ b/src/com/android/settings/wifi/AccessPoint.java
@@ -25,6 +25,7 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.os.Bundle;
import android.preference.Preference;
import android.view.View;
import android.widget.ImageView;
@@ -32,6 +33,12 @@ import android.widget.ImageView;
import java.util.Comparator;
class AccessPoint extends Preference {
+
+ private static final String KEY_DETAILEDSTATE = "key_detailedstate";
+ private static final String KEY_WIFIINFO = "key_wifiinfo";
+ private static final String KEY_SCANRESULT = "key_scanresult";
+ private static final String KEY_CONFIG = "key_config";
+
private static final int[] STATE_SECURED = {R.attr.state_encrypted};
private static final int[] STATE_NONE = {};
@@ -41,13 +48,15 @@ class AccessPoint extends Preference {
static final int SECURITY_PSK = 2;
static final int SECURITY_EAP = 3;
- final String ssid;
- final String bssid;
- final int security;
- final int networkId;
+ String ssid;
+ String bssid;
+ int security;
+ int networkId;
boolean wpsAvailable = false;
private WifiConfiguration mConfig;
+ /*package*/ScanResult mScanResult;
+
private int mRssi;
private WifiInfo mInfo;
private DetailedState mState;
@@ -78,24 +87,60 @@ class AccessPoint extends Preference {
AccessPoint(Context context, WifiConfiguration config) {
super(context);
setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
+ loadConfig(config);
+ }
+
+ AccessPoint(Context context, ScanResult result) {
+ super(context);
+ setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
+ loadResult(result);
+ }
+
+ AccessPoint(Context context, Bundle savedState) {
+ super(context);
+ setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
+
+ mConfig = savedState.getParcelable(KEY_CONFIG);
+ if (mConfig != null) {
+ loadConfig(mConfig);
+ }
+ mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT);
+ if (mScanResult != null) {
+ loadResult(mScanResult);
+ }
+ mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
+ if (savedState.containsKey(KEY_DETAILEDSTATE)) {
+ mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE));
+ }
+ update(mInfo, mState);
+ }
+
+ public void saveWifiState(Bundle savedState) {
+ savedState.putParcelable(KEY_CONFIG, mConfig);
+ savedState.putParcelable(KEY_SCANRESULT, mScanResult);
+ savedState.putParcelable(KEY_WIFIINFO, mInfo);
+ if (mState != null) {
+ savedState.putString(KEY_DETAILEDSTATE, mState.toString());
+ }
+ }
+
+ private void loadConfig(WifiConfiguration config) {
ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
bssid = config.BSSID;
security = getSecurity(config);
networkId = config.networkId;
- mConfig = config;
mRssi = Integer.MAX_VALUE;
+ mConfig = config;
}
- AccessPoint(Context context, ScanResult result) {
- super(context);
- setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
+ private void loadResult(ScanResult result) {
ssid = result.SSID;
bssid = result.BSSID;
security = getSecurity(result);
- wpsAvailable = security != SECURITY_EAP &&
- result.capabilities.contains("WPS");
+ wpsAvailable = security != SECURITY_EAP && result.capabilities.contains("WPS");
networkId = -1;
mRssi = result.level;
+ mScanResult = result;
}
@Override
diff --git a/src/com/android/settings/wifi/AdvancedSettings.java b/src/com/android/settings/wifi/AdvancedSettings.java
deleted file mode 100644
index faea9f2..0000000
--- a/src/com/android/settings/wifi/AdvancedSettings.java
+++ /dev/null
@@ -1,112 +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 com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.os.SystemProperties;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.text.TextUtils;
-import android.widget.Toast;
-import android.util.Log;
-
-public class AdvancedSettings extends SettingsPreferenceFragment
- implements Preference.OnPreferenceChangeListener {
-
- private static final String TAG = "AdvancedSettings";
- private static final String KEY_MAC_ADDRESS = "mac_address";
- private static final String KEY_CURRENT_IP_ADDRESS = "current_ip_address";
- private static final String KEY_FREQUENCY_BAND = "frequency_band";
-
- private WifiManager mWifiManager;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.wifi_advanced_settings);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- initPreferences();
- refreshWifiInfo();
- }
-
- private void initPreferences() {
-
- ListPreference pref = (ListPreference) findPreference(KEY_FREQUENCY_BAND);
-
- if (mWifiManager.isDualBandSupported()) {
- pref.setOnPreferenceChangeListener(this);
- int value = mWifiManager.getFrequencyBand();
- if (value != -1) {
- pref.setValue(String.valueOf(value));
- } else {
- Log.e(TAG, "Failed to fetch frequency band");
- }
- } else {
- getPreferenceScreen().removePreference(pref);
- }
- }
-
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- String key = preference.getKey();
- if (key == null) return true;
-
- if (key.equals(KEY_FREQUENCY_BAND)) {
- try {
- mWifiManager.setFrequencyBand(Integer.parseInt(((String) newValue)), true);
- } catch (NumberFormatException e) {
- Toast.makeText(getActivity(), R.string.wifi_setting_frequency_band_error,
- Toast.LENGTH_SHORT).show();
- return false;
- }
- }
-
- return true;
- }
-
- private void refreshWifiInfo() {
- WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-
- Preference wifiMacAddressPref = findPreference(KEY_MAC_ADDRESS);
- String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
- wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress
- : getActivity().getString(R.string.status_unavailable));
-
- Preference wifiIpAddressPref = findPreference(KEY_CURRENT_IP_ADDRESS);
- String ipAddress = Utils.getWifiIpAddresses(getActivity());
- wifiIpAddressPref.setSummary(ipAddress == null ?
- getActivity().getString(R.string.status_unavailable) : ipAddress);
- }
-
-}
diff --git a/src/com/android/settings/wifi/AdvancedWifiSettings.java b/src/com/android/settings/wifi/AdvancedWifiSettings.java
new file mode 100644
index 0000000..6c983fd
--- /dev/null
+++ b/src/com/android/settings/wifi/AdvancedWifiSettings.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+
+public class AdvancedWifiSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String TAG = "AdvancedWifiSettings";
+ private static final String KEY_MAC_ADDRESS = "mac_address";
+ private static final String KEY_CURRENT_IP_ADDRESS = "current_ip_address";
+ private static final String KEY_FREQUENCY_BAND = "frequency_band";
+ private static final String KEY_NOTIFY_OPEN_NETWORKS = "notify_open_networks";
+ private static final String KEY_SLEEP_POLICY = "sleep_policy";
+ private static final String KEY_ENABLE_WIFI_WATCHDOG = "wifi_enable_watchdog_service";
+
+ private WifiManager mWifiManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.wifi_advanced_settings);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ initPreferences();
+ refreshWifiInfo();
+ }
+
+ private void initPreferences() {
+ CheckBoxPreference notifyOpenNetworks =
+ (CheckBoxPreference) findPreference(KEY_NOTIFY_OPEN_NETWORKS);
+ notifyOpenNetworks.setChecked(Secure.getInt(getContentResolver(),
+ Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
+ notifyOpenNetworks.setEnabled(mWifiManager.isWifiEnabled());
+
+ CheckBoxPreference watchdogEnabled =
+ (CheckBoxPreference) findPreference(KEY_ENABLE_WIFI_WATCHDOG);
+ watchdogEnabled.setChecked(Secure.getInt(getContentResolver(),
+ Secure.WIFI_WATCHDOG_ON, 1) == 1);
+
+ watchdogEnabled.setEnabled(mWifiManager.isWifiEnabled());
+
+ ListPreference frequencyPref = (ListPreference) findPreference(KEY_FREQUENCY_BAND);
+
+ if (mWifiManager.isDualBandSupported()) {
+ frequencyPref.setOnPreferenceChangeListener(this);
+ int value = mWifiManager.getFrequencyBand();
+ if (value != -1) {
+ frequencyPref.setValue(String.valueOf(value));
+ } else {
+ Log.e(TAG, "Failed to fetch frequency band");
+ }
+ } else {
+ if (frequencyPref != null) {
+ // null if it has already been removed before resume
+ getPreferenceScreen().removePreference(frequencyPref);
+ }
+ }
+
+ ListPreference sleepPolicyPref = (ListPreference) findPreference(KEY_SLEEP_POLICY);
+ if (sleepPolicyPref != null) {
+ if (Utils.isWifiOnly()) {
+ sleepPolicyPref.setEntries(R.array.wifi_sleep_policy_entries_wifi_only);
+ sleepPolicyPref.setSummary(R.string.wifi_setting_sleep_policy_summary_wifi_only);
+ }
+ sleepPolicyPref.setOnPreferenceChangeListener(this);
+ int value = Settings.System.getInt(getContentResolver(),
+ Settings.System.WIFI_SLEEP_POLICY,
+ Settings.System.WIFI_SLEEP_POLICY_NEVER);
+ sleepPolicyPref.setValue(String.valueOf(value));
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
+ String key = preference.getKey();
+
+ if (KEY_NOTIFY_OPEN_NETWORKS.equals(key)) {
+ Secure.putInt(getContentResolver(),
+ Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ ((CheckBoxPreference) preference).isChecked() ? 1 : 0);
+ } else if (KEY_ENABLE_WIFI_WATCHDOG.equals(key)) {
+ Secure.putInt(getContentResolver(),
+ Secure.WIFI_WATCHDOG_ON,
+ ((CheckBoxPreference) preference).isChecked() ? 1 : 0);
+ } else {
+ return super.onPreferenceTreeClick(screen, preference);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ String key = preference.getKey();
+
+ if (KEY_FREQUENCY_BAND.equals(key)) {
+ try {
+ mWifiManager.setFrequencyBand(Integer.parseInt(((String) newValue)), true);
+ } catch (NumberFormatException e) {
+ Toast.makeText(getActivity(), R.string.wifi_setting_frequency_band_error,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+
+ if (KEY_SLEEP_POLICY.equals(key)) {
+ try {
+ Settings.System.putInt(getContentResolver(),
+ Settings.System.WIFI_SLEEP_POLICY, Integer.parseInt(((String) newValue)));
+ } catch (NumberFormatException e) {
+ Toast.makeText(getActivity(), R.string.wifi_setting_sleep_policy_error,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void refreshWifiInfo() {
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+
+ Preference wifiMacAddressPref = findPreference(KEY_MAC_ADDRESS);
+ String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+ wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress
+ : getActivity().getString(R.string.status_unavailable));
+
+ Preference wifiIpAddressPref = findPreference(KEY_CURRENT_IP_ADDRESS);
+ String ipAddress = Utils.getWifiIpAddresses(getActivity());
+ wifiIpAddressPref.setSummary(ipAddress == null ?
+ getActivity().getString(R.string.status_unavailable) : ipAddress);
+ }
+
+}
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 91f4110..876fd99 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -16,33 +16,29 @@
package com.android.settings.wifi;
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+
import android.content.Context;
-import android.content.DialogInterface;
import android.content.res.Resources;
-import android.net.DhcpInfo;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkUtils;
-import android.net.Proxy;
import android.net.ProxyProperties;
import android.net.RouteInfo;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.AuthAlgorithm;
+import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WpsConfiguration;
-import android.net.wifi.WpsConfiguration.Setup;
-
-import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
import android.net.wifi.WifiConfiguration.ProxySettings;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WpsConfiguration;
+import android.net.wifi.WpsConfiguration.Setup;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
-import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -57,12 +53,10 @@ import com.android.settings.ProxySelector;
import com.android.settings.R;
import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
import java.util.Iterator;
/**
- * The class for allowing UIs like {@link WifiDialog} and {@link WifiConfigPreference} to
+ * The class for allowing UIs like {@link WifiDialog} and {@link WifiConfigUiBase} to
* share the logic for controlling buttons, text fields, etc.
*/
public class WifiConfigController implements TextWatcher,
@@ -96,8 +90,8 @@ public class WifiConfigController implements TextWatcher,
/* These values come from "wifi_network_setup" resource array */
public static final int MANUAL = 0;
public static final int WPS_PBC = 1;
- public static final int WPS_PIN_FROM_ACCESS_POINT = 2;
- public static final int WPS_PIN_FROM_DEVICE = 3;
+ public static final int WPS_KEYPAD = 2;
+ public static final int WPS_DISPLAY = 3;
/* These values come from "wifi_proxy_settings" resource array */
public static final int PROXY_NONE = 0;
@@ -118,8 +112,8 @@ public class WifiConfigController implements TextWatcher,
private TextView mProxyPortView;
private TextView mProxyExclusionListView;
- private IpAssignment mIpAssignment;
- private ProxySettings mProxySettings;
+ private IpAssignment mIpAssignment = IpAssignment.UNASSIGNED;
+ private ProxySettings mProxySettings = ProxySettings.UNASSIGNED;
private LinkProperties mLinkProperties = new LinkProperties();
// True when this instance is used in SetupWizard XL context.
@@ -429,7 +423,9 @@ public class WifiConfigController implements TextWatcher,
int networkPrefixLength = -1;
try {
networkPrefixLength = Integer.parseInt(mNetworkPrefixLengthView.getText().toString());
- } catch (NumberFormatException e) { }
+ } catch (NumberFormatException e) {
+ // Use -1
+ }
if (networkPrefixLength < 0 || networkPrefixLength > 32) {
return R.string.wifi_ip_settings_invalid_network_prefix_length;
}
@@ -477,11 +473,11 @@ public class WifiConfigController implements TextWatcher,
case WPS_PBC:
config.setup = Setup.PBC;
break;
- case WPS_PIN_FROM_ACCESS_POINT:
- config.setup = Setup.PIN_FROM_ACCESS_POINT;
+ case WPS_KEYPAD:
+ config.setup = Setup.KEYPAD;
break;
- case WPS_PIN_FROM_DEVICE:
- config.setup = Setup.PIN_FROM_DEVICE;
+ case WPS_DISPLAY:
+ config.setup = Setup.DISPLAY;
break;
default:
config.setup = Setup.INVALID;
@@ -563,14 +559,14 @@ public class WifiConfigController implements TextWatcher,
int pos = mNetworkSetupSpinner.getSelectedItemPosition();
/* Show pin text input if needed */
- if (pos == WPS_PIN_FROM_ACCESS_POINT) {
+ if (pos == WPS_DISPLAY) {
mView.findViewById(R.id.wps_fields).setVisibility(View.VISIBLE);
} else {
mView.findViewById(R.id.wps_fields).setVisibility(View.GONE);
}
/* show/hide manual security fields appropriately */
- if ((pos == WPS_PIN_FROM_ACCESS_POINT) || (pos == WPS_PIN_FROM_DEVICE)
+ if ((pos == WPS_DISPLAY) || (pos == WPS_KEYPAD)
|| (pos == WPS_PBC)) {
mView.findViewById(R.id.security_fields).setVisibility(View.GONE);
} else {
@@ -698,6 +694,7 @@ public class WifiConfigController implements TextWatcher,
private void setSelection(Spinner spinner, String value) {
if (value != null) {
+ @SuppressWarnings("unchecked")
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinner.getAdapter();
for (int i = adapter.getCount() - 1; i >= 0; --i) {
if (value.equals(adapter.getItem(i))) {
@@ -719,10 +716,12 @@ public class WifiConfigController implements TextWatcher,
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // work done in afterTextChanged
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // work done in afterTextChanged
}
@Override
@@ -750,5 +749,6 @@ public class WifiConfigController implements TextWatcher,
@Override
public void onNothingSelected(AdapterView<?> parent) {
+ //
}
}
diff --git a/src/com/android/settings/wifi/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java
index 7f1221e..223022d 100644
--- a/src/com/android/settings/wifi/WifiEnabler.java
+++ b/src/com/android/settings/wifi/WifiEnabler.java
@@ -16,9 +16,6 @@
package com.android.settings.wifi;
-import com.android.settings.R;
-import com.android.settings.WirelessSettings;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -27,19 +24,19 @@ import android.net.NetworkInfo;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.preference.Preference;
-import android.preference.CheckBoxPreference;
import android.provider.Settings;
-import android.text.TextUtils;
+import android.widget.CompoundButton;
+import android.widget.Switch;
import android.widget.Toast;
+import com.android.settings.R;
+import com.android.settings.WirelessSettings;
+
import java.util.concurrent.atomic.AtomicBoolean;
-public class WifiEnabler implements Preference.OnPreferenceChangeListener {
+public class WifiEnabler implements CompoundButton.OnCheckedChangeListener {
private final Context mContext;
- private final CheckBoxPreference mCheckBox;
- private final CharSequence mOriginalSummary;
-
+ private Switch mSwitch;
private AtomicBoolean mConnected = new AtomicBoolean(false);
private final WifiManager mWifiManager;
@@ -65,11 +62,9 @@ public class WifiEnabler implements Preference.OnPreferenceChangeListener {
}
};
- public WifiEnabler(Context context, CheckBoxPreference checkBox) {
+ public WifiEnabler(Context context, Switch switch_) {
mContext = context;
- mCheckBox = checkBox;
- mOriginalSummary = checkBox.getSummary();
- checkBox.setPersistent(false);
+ mSwitch = switch_;
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
@@ -81,78 +76,86 @@ public class WifiEnabler implements Preference.OnPreferenceChangeListener {
public void resume() {
// Wi-Fi state is sticky, so just let the receiver update UI
mContext.registerReceiver(mReceiver, mIntentFilter);
- mCheckBox.setOnPreferenceChangeListener(this);
+ mSwitch.setOnCheckedChangeListener(this);
}
public void pause() {
mContext.unregisterReceiver(mReceiver);
- mCheckBox.setOnPreferenceChangeListener(null);
+ mSwitch.setOnCheckedChangeListener(null);
}
- public boolean onPreferenceChange(Preference preference, Object value) {
- boolean enable = (Boolean) value;
+ public void setSwitch(Switch switch_) {
+ if (mSwitch == switch_) return;
+ mSwitch.setOnCheckedChangeListener(null);
+ mSwitch = switch_;
+ mSwitch.setOnCheckedChangeListener(this);
+
+ final int wifiState = mWifiManager.getWifiState();
+ boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED;
+ boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED;
+ mSwitch.setChecked(isEnabled);
+ mSwitch.setEnabled(isEnabled || isDisabled);
+ }
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Show toast message if Wi-Fi is not allowed in airplane mode
- if (enable && !WirelessSettings
- .isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) {
- Toast.makeText(mContext, R.string.wifi_in_airplane_mode,
- Toast.LENGTH_SHORT).show();
- return false;
+ if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) {
+ Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
+ // Reset switch to off. No infinite check/listenenr loop.
+ buttonView.setChecked(false);
}
- /**
- * Disable tethering if enabling Wifi
- */
+ // Disable tethering if enabling Wifi
int wifiApState = mWifiManager.getWifiApState();
- if (enable && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
+ if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
(wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
mWifiManager.setWifiApEnabled(null, false);
}
- if (mWifiManager.setWifiEnabled(enable)) {
- mCheckBox.setEnabled(false);
+
+ if (mWifiManager.setWifiEnabled(isChecked)) {
+ // Intent has been taken into account, disable until new state is active
+ mSwitch.setEnabled(false);
} else {
- mCheckBox.setSummary(R.string.wifi_error);
+ // Error
+ Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
}
-
- // Don't update UI to opposite state until we're sure
- return false;
}
private void handleWifiStateChanged(int state) {
switch (state) {
case WifiManager.WIFI_STATE_ENABLING:
- mCheckBox.setSummary(R.string.wifi_starting);
- mCheckBox.setEnabled(false);
+ mSwitch.setEnabled(false);
break;
case WifiManager.WIFI_STATE_ENABLED:
- mCheckBox.setChecked(true);
- mCheckBox.setSummary(null);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(true);
+ mSwitch.setEnabled(true);
break;
case WifiManager.WIFI_STATE_DISABLING:
- mCheckBox.setSummary(R.string.wifi_stopping);
- mCheckBox.setEnabled(false);
+ mSwitch.setEnabled(false);
break;
case WifiManager.WIFI_STATE_DISABLED:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(mOriginalSummary);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(true);
break;
default:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(R.string.wifi_error);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(true);
}
}
- private void handleStateChanged(NetworkInfo.DetailedState state) {
+ private void handleStateChanged(@SuppressWarnings("unused") NetworkInfo.DetailedState state) {
+ // After the refactoring from a CheckBoxPreference to a Switch, this method is useless since
+ // there is nowhere to display a summary.
+ // This code is kept in case a future change re-introduces an associated text.
+ /*
// WifiInfo is valid if and only if Wi-Fi is enabled.
- // Here we use the state of the check box as an optimization.
- if (state != null && mCheckBox.isChecked()) {
+ // Here we use the state of the switch as an optimization.
+ if (state != null && mSwitch.isChecked()) {
WifiInfo info = mWifiManager.getConnectionInfo();
if (info != null) {
- mCheckBox.setSummary(Summary.get(mContext, info.getSSID(), state));
+ //setSummary(Summary.get(mContext, info.getSSID(), state));
}
}
+ */
}
}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index ab5e686..b3259e0 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -18,8 +18,10 @@ package com.android.settings.wifi;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -31,40 +33,36 @@ import android.net.NetworkInfo.DetailedState;
import android.net.wifi.ScanResult;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WpsResult;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WpsConfiguration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.preference.CheckBoxPreference;
import android.preference.Preference;
-import android.preference.ListPreference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
-import android.provider.Settings.Secure;
-import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.Log;
import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
import com.android.internal.util.AsyncChannel;
-import com.android.settings.ProgressCategoryBase;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
import java.util.ArrayList;
import java.util.Collection;
@@ -83,14 +81,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
* other decorations specific to that screen.
*/
public class WifiSettings extends SettingsPreferenceFragment
- implements DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener {
+ implements DialogInterface.OnClickListener {
private static final String TAG = "WifiSettings";
private static final int MENU_ID_SCAN = Menu.FIRST;
- private static final int MENU_ID_ADVANCED = Menu.FIRST + 1;
- private static final int MENU_ID_CONNECT = Menu.FIRST + 2;
- private static final int MENU_ID_FORGET = Menu.FIRST + 3;
- private static final int MENU_ID_MODIFY = Menu.FIRST + 4;
- private static final String KEY_SLEEP_POLICY = "sleep_policy";
+ private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 1;
+ private static final int MENU_ID_ADVANCED = Menu.FIRST + 2;
+ private static final int MENU_ID_CONNECT = Menu.FIRST + 3;
+ private static final int MENU_ID_FORGET = Menu.FIRST + 4;
+ private static final int MENU_ID_MODIFY = Menu.FIRST + 5;
+
+ private static final int WIFI_DIALOG_ID = 1;
+
+ // Instance state keys
+ private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
+ private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
private final IntentFilter mFilter;
private final BroadcastReceiver mReceiver;
@@ -98,12 +102,8 @@ public class WifiSettings extends SettingsPreferenceFragment
private WifiManager mWifiManager;
private WifiEnabler mWifiEnabler;
- private CheckBoxPreference mNotifyOpenNetworks;
- private ProgressCategoryBase mAccessPoints;
- private Preference mAddNetwork;
// An access point being editted is stored here.
private AccessPoint mSelectedAccessPoint;
- private boolean mEdit;
private DetailedState mLastState;
private WifiInfo mLastInfo;
@@ -114,6 +114,9 @@ public class WifiSettings extends SettingsPreferenceFragment
private WifiDialog mDialog;
+ private View mView;
+ private TextView mEmptyView;
+
/* Used in Wifi Setup context */
// this boolean extra specifies whether to disable the Next button when not connected
@@ -123,6 +126,11 @@ public class WifiSettings extends SettingsPreferenceFragment
private boolean mEnableNextOnConnection;
private boolean mInXlSetupWizard;
+ // Save the dialog details
+ private boolean mDlgEdit;
+ private AccessPoint mDlgAccessPoint;
+ private Bundle mAccessPointSavedState;
+
/* End of "used in Wifi Setup context" */
public WifiSettings() {
@@ -157,11 +165,8 @@ public class WifiSettings extends SettingsPreferenceFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- if (mInXlSetupWizard) {
- return inflater.inflate(R.layout.custom_preference_list_fragment, container, false);
- } else {
- return super.onCreateView(inflater, container, savedInstanceState);
- }
+ mView = inflater.inflate(R.layout.custom_preference_list_fragment, container, false);
+ return mView;
}
@Override
@@ -172,6 +177,11 @@ public class WifiSettings extends SettingsPreferenceFragment
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
mWifiManager.asyncConnect(getActivity(), new WifiServiceHandler());
+ if (savedInstanceState != null
+ && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
+ mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
+ mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
+ }
final Activity activity = getActivity();
final Intent intent = activity.getIntent();
@@ -180,60 +190,51 @@ public class WifiSettings extends SettingsPreferenceFragment
// state, start it off in the right state
mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
- // Avoid re-adding on returning from an overlapping activity/fragment.
- if (getPreferenceScreen() == null || getPreferenceScreen().getPreferenceCount() < 2) {
- if (mEnableNextOnConnection) {
- if (hasNextButton()) {
- final ConnectivityManager connectivity = (ConnectivityManager)
- getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
- if (connectivity != null) {
- NetworkInfo info = connectivity.getNetworkInfo(
- ConnectivityManager.TYPE_WIFI);
- changeNextButtonState(info.isConnected());
- }
+ if (mEnableNextOnConnection) {
+ if (hasNextButton()) {
+ final ConnectivityManager connectivity = (ConnectivityManager)
+ getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivity != null) {
+ NetworkInfo info = connectivity.getNetworkInfo(
+ ConnectivityManager.TYPE_WIFI);
+ changeNextButtonState(info.isConnected());
}
}
+ }
- if (mInXlSetupWizard) {
- addPreferencesFromResource(R.xml.wifi_access_points_for_wifi_setup_xl);
- } else if (intent.getBooleanExtra("only_access_points", false)) {
- addPreferencesFromResource(R.xml.wifi_access_points);
- } else {
- addPreferencesFromResource(R.xml.wifi_settings);
- mWifiEnabler = new WifiEnabler(activity,
- (CheckBoxPreference) findPreference("enable_wifi"));
- mNotifyOpenNetworks =
- (CheckBoxPreference) findPreference("notify_open_networks");
- mNotifyOpenNetworks.setChecked(Secure.getInt(getContentResolver(),
- Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
- }
- // This may be either ProgressCategory or AccessPointCategoryForXL.
- final ProgressCategoryBase preference =
- (ProgressCategoryBase) findPreference("access_points");
- mAccessPoints = preference;
- mAccessPoints.setOrderingAsAdded(false);
- mAddNetwork = findPreference("add_network");
-
- ListPreference pref = (ListPreference) findPreference(KEY_SLEEP_POLICY);
- if (pref != null) {
- if (Utils.isWifiOnly()) {
- pref.setEntries(R.array.wifi_sleep_policy_entries_wifi_only);
- pref.setSummary(R.string.wifi_setting_sleep_policy_summary_wifi_only);
+ if (mInXlSetupWizard) {
+ addPreferencesFromResource(R.xml.wifi_access_points_for_wifi_setup_xl);
+ } else {
+ addPreferencesFromResource(R.xml.wifi_settings);
+
+ Switch actionBarSwitch = new Switch(activity);
+
+ if (activity instanceof PreferenceActivity) {
+ PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
+ final int padding = activity.getResources().getDimensionPixelSize(
+ R.dimen.action_bar_switch_padding);
+ actionBarSwitch.setPadding(0, 0, padding, 0);
+ activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM);
+ activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT));
}
- pref.setOnPreferenceChangeListener(this);
- int value = Settings.System.getInt(getContentResolver(),
- Settings.System.WIFI_SLEEP_POLICY,
- Settings.System.WIFI_SLEEP_POLICY_NEVER);
- pref.setValue(String.valueOf(value));
}
- registerForContextMenu(getListView());
- setHasOptionsMenu(true);
+ mWifiEnabler = new WifiEnabler(activity, actionBarSwitch);
}
+ mEmptyView = (TextView) mView.findViewById(R.id.empty);
+ getListView().setEmptyView(mEmptyView);
+
+ registerForContextMenu(getListView());
+ setHasOptionsMenu(true);
+
// After confirming PreferenceScreen is available, we call super.
super.onActivityCreated(savedInstanceState);
-
}
@Override
@@ -245,10 +246,11 @@ public class WifiSettings extends SettingsPreferenceFragment
getActivity().registerReceiver(mReceiver, mFilter);
if (mKeyStoreNetworkId != INVALID_NETWORK_ID &&
- KeyStore.getInstance().test() == KeyStore.NO_ERROR) {
+ KeyStore.getInstance().state() == KeyStore.State.UNLOCKED) {
mWifiManager.connectNetwork(mKeyStoreNetworkId);
}
mKeyStoreNetworkId = INVALID_NETWORK_ID;
+
updateAccessPoints();
}
@@ -260,25 +262,43 @@ public class WifiSettings extends SettingsPreferenceFragment
}
getActivity().unregisterReceiver(mReceiver);
mScanner.pause();
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// We don't want menus in Setup Wizard XL.
if (!mInXlSetupWizard) {
+ final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
- .setIcon(R.drawable.ic_menu_scan_network);
+ //.setIcon(R.drawable.ic_menu_scan_network)
+ .setEnabled(wifiIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
+ .setEnabled(wifiIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
- .setIcon(android.R.drawable.ic_menu_manage);
+ //.setIcon(android.R.drawable.ic_menu_manage)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ // If the dialog is showing, save its state.
+ if (mDialog != null && mDialog.isShowing()) {
+ outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
+ if (mDlgAccessPoint != null) {
+ mAccessPointSavedState = new Bundle();
+ mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
+ outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
+ }
+ }
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ID_SCAN:
@@ -286,15 +306,20 @@ public class WifiSettings extends SettingsPreferenceFragment
mScanner.forceScan();
}
return true;
+ case MENU_ID_ADD_NETWORK:
+ if (mWifiManager.isWifiEnabled()) {
+ onAddNetworkPressed();
+ }
+ return true;
case MENU_ID_ADVANCED:
if (getActivity() instanceof PreferenceActivity) {
((PreferenceActivity) getActivity()).startPreferencePanel(
- AdvancedSettings.class.getCanonicalName(),
+ AdvancedWifiSettings.class.getCanonicalName(),
null,
R.string.wifi_advanced_titlebar, null,
this, 0);
} else {
- startFragment(this, AdvancedSettings.class.getCanonicalName(), -1, null);
+ startFragment(this, AdvancedWifiSettings.class.getCanonicalName(), -1, null);
}
return true;
}
@@ -363,43 +388,17 @@ public class WifiSettings extends SettingsPreferenceFragment
if (preference instanceof AccessPoint) {
mSelectedAccessPoint = (AccessPoint) preference;
showConfigUi(mSelectedAccessPoint, false);
- } else if (preference == mAddNetwork) {
- onAddNetworkPressed();
- } else if (preference == mNotifyOpenNetworks) {
- Secure.putInt(getContentResolver(),
- Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- mNotifyOpenNetworks.isChecked() ? 1 : 0);
} else {
return super.onPreferenceTreeClick(screen, preference);
}
return true;
}
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- String key = preference.getKey();
- if (key == null) return true;
-
- if (key.equals(KEY_SLEEP_POLICY)) {
- try {
- Settings.System.putInt(getContentResolver(),
- Settings.System.WIFI_SLEEP_POLICY, Integer.parseInt(((String) newValue)));
- } catch (NumberFormatException e) {
- Toast.makeText(getActivity(), R.string.wifi_setting_sleep_policy_error,
- Toast.LENGTH_SHORT).show();
- return false;
- }
- }
-
- return true;
- }
-
-
/**
* Shows an appropriate Wifi configuration component.
* Called when a user clicks "Add network" preference or one of available networks is selected.
*/
private void showConfigUi(AccessPoint accessPoint, boolean edit) {
- mEdit = edit;
if (mInXlSetupWizard) {
((WifiSettingsForSetupWizardXL)getActivity()).showConfigUi(accessPoint, edit);
} else {
@@ -409,15 +408,36 @@ public class WifiSettings extends SettingsPreferenceFragment
private void showDialog(AccessPoint accessPoint, boolean edit) {
if (mDialog != null) {
- mDialog.dismiss();
+ removeDialog(WIFI_DIALOG_ID);
+ mDialog = null;
}
- mDialog = new WifiDialog(getActivity(), this, accessPoint, edit);
- mDialog.show();
+
+ // Save the access point and edit mode
+ mDlgAccessPoint = accessPoint;
+ mDlgEdit = edit;
+
+ showDialog(WIFI_DIALOG_ID);
+ }
+
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ AccessPoint ap = mDlgAccessPoint; // For manual launch
+ if (ap == null) { // For re-launch from saved state
+ if (mAccessPointSavedState != null) {
+ ap = new AccessPoint(getActivity(), mAccessPointSavedState);
+ // For repeated orientation changes
+ mDlgAccessPoint = ap;
+ }
+ }
+ // If it's still null, fine, it's for Add Network
+ mSelectedAccessPoint = ap;
+ mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
+ return mDialog;
}
private boolean requireKeyStore(WifiConfiguration config) {
if (WifiConfigController.requireKeyStore(config) &&
- KeyStore.getInstance().test() != KeyStore.NO_ERROR) {
+ KeyStore.getInstance().state() != KeyStore.State.UNLOCKED) {
mKeyStoreNetworkId = config.networkId;
Credentials.getInstance().unlock(getActivity());
return true;
@@ -430,20 +450,42 @@ public class WifiSettings extends SettingsPreferenceFragment
* the strength of network and the security for it.
*/
private void updateAccessPoints() {
- mAccessPoints.removeAll();
+ final int wifiState = mWifiManager.getWifiState();
+
+ switch (wifiState) {
+ case WifiManager.WIFI_STATE_ENABLED:
+ getPreferenceScreen().removeAll();
+ // AccessPoints are automatically sorted with TreeSet.
+ final Collection<AccessPoint> accessPoints = constructAccessPoints();
+ if (mInXlSetupWizard) {
+ ((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated(
+ getPreferenceScreen(), accessPoints);
+ } else {
+ for (AccessPoint accessPoint : accessPoints) {
+ getPreferenceScreen().addPreference(accessPoint);
+ }
+ }
+ break;
- // AccessPoints are automatically sorted with TreeSet.
- final Collection<AccessPoint> accessPoints = constructAccessPoints();
- if (mInXlSetupWizard) {
- ((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated(
- mAccessPoints, accessPoints);
- } else {
- for (AccessPoint accessPoint : accessPoints) {
- mAccessPoints.addPreference(accessPoint);
- }
+ case WifiManager.WIFI_STATE_ENABLING:
+ getPreferenceScreen().removeAll();
+ break;
+
+ case WifiManager.WIFI_STATE_DISABLING:
+ addMessagePreference(R.string.wifi_stopping);
+ break;
+
+ case WifiManager.WIFI_STATE_DISABLED:
+ addMessagePreference(R.string.wifi_empty_list_wifi_off);
+ break;
}
}
+ private void addMessagePreference(int messageId) {
+ if (mEmptyView != null) mEmptyView.setText(messageId);
+ getPreferenceScreen().removeAll();
+ }
+
private Collection<AccessPoint> constructAccessPoints() {
Collection<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
@@ -542,9 +584,9 @@ public class WifiSettings extends SettingsPreferenceFragment
mLastState = state;
}
- for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
+ for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
// Maybe there's a WifiConfigPreference
- Preference preference = mAccessPoints.getPreference(i);
+ Preference preference = getPreferenceScreen().getPreference(i);
if (preference instanceof AccessPoint) {
final AccessPoint accessPoint = (AccessPoint) preference;
accessPoint.update(mLastInfo, mLastState);
@@ -557,12 +599,25 @@ public class WifiSettings extends SettingsPreferenceFragment
}
private void updateWifiState(int state) {
- if (state == WifiManager.WIFI_STATE_ENABLED) {
- mScanner.resume();
- } else {
- mScanner.pause();
- mAccessPoints.removeAll();
+ getActivity().invalidateOptionsMenu();
+
+ switch (state) {
+ case WifiManager.WIFI_STATE_ENABLED:
+ mScanner.resume();
+ return; // not break, to avoid the call to pause() below
+
+ case WifiManager.WIFI_STATE_ENABLING:
+ addMessagePreference(R.string.wifi_starting);
+ break;
+
+ case WifiManager.WIFI_STATE_DISABLED:
+ addMessagePreference(R.string.wifi_empty_list_wifi_off);
+ break;
}
+
+ mLastInfo = null;
+ mLastState = null;
+ mScanner.pause();
}
private class Scanner extends Handler {
@@ -580,7 +635,6 @@ public class WifiSettings extends SettingsPreferenceFragment
void pause() {
mRetry = 0;
- mAccessPoints.setProgress(false);
removeMessages(0);
}
@@ -594,7 +648,6 @@ public class WifiSettings extends SettingsPreferenceFragment
Toast.LENGTH_LONG).show();
return;
}
- mAccessPoints.setProgress(mRetry != 0);
// Combo scans can take 5-6s to complete. Increase interval to 10s.
sendEmptyMessageDelayed(0, 10000);
}
@@ -636,6 +689,7 @@ public class WifiSettings extends SettingsPreferenceFragment
}
break;
}
+ break;
//TODO: more connectivity feedback
default:
//Ignore
@@ -679,8 +733,8 @@ public class WifiSettings extends SettingsPreferenceFragment
int networkSetup = configController.chosenNetworkSetupMethod();
switch(networkSetup) {
case WifiConfigController.WPS_PBC:
- case WifiConfigController.WPS_PIN_FROM_ACCESS_POINT:
- case WifiConfigController.WPS_PIN_FROM_DEVICE:
+ case WifiConfigController.WPS_DISPLAY:
+ case WifiConfigController.WPS_KEYPAD:
mWifiManager.startWps(configController.getWpsConfig());
break;
case WifiConfigController.MANUAL:
@@ -740,7 +794,7 @@ public class WifiSettings extends SettingsPreferenceFragment
mScanner.resume();
}
- mAccessPoints.removeAll();
+ getPreferenceScreen().removeAll();
}
/**
@@ -753,8 +807,9 @@ public class WifiSettings extends SettingsPreferenceFragment
}
/* package */ int getAccessPointsCount() {
- if (mAccessPoints != null) {
- return mAccessPoints.getPreferenceCount();
+ final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
+ if (wifiIsEnabled) {
+ return getPreferenceScreen().getPreferenceCount();
} else {
return 0;
}
diff --git a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java
index a73f96c..a3f1764 100644
--- a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java
+++ b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java
@@ -16,8 +16,6 @@
package com.android.settings.wifi;
-import com.android.settings.R;
-
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -28,11 +26,9 @@ import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.preference.PreferenceCategory;
+import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -43,6 +39,7 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.util.AsyncChannel;
+import com.android.settings.R;
import java.util.Collection;
import java.util.EnumMap;
@@ -75,12 +72,6 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
sNetworkStateMap.put(DetailedState.FAILED, DetailedState.FAILED);
}
- /**
- * Used with {@link Button#setTag(Object)} to remember "Connect" button is pressed in
- * with "add network" flow.
- */
- private static final int CONNECT_BUTTON_TAG_ADD_NETWORK = 1;
-
private WifiSettings mWifiSettings;
private WifiManager mWifiManager;
@@ -166,7 +157,7 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
// At first, Wifi module doesn't return SCANNING state (it's too early), so we manually
// show it.
- showScanningProgressBar();
+ showScanningState();
}
private void initViews() {
@@ -291,17 +282,16 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
switch (state) {
case SCANNING: {
- // Let users know the device is working correctly though currently there's
- // no visible network on the list.
- if (mWifiSettings.getAccessPointsCount() == 0) {
- showScanningState();
- } else {
- // Users already see available networks.
- showDisconnectedProgressBar();
- if (mScreenState == SCREEN_STATE_DISCONNECTED) {
+ if (mScreenState == SCREEN_STATE_DISCONNECTED) {
+ if (mWifiSettings.getAccessPointsCount() == 0) {
+ showScanningState();
+ } else {
+ showDisconnectedProgressBar();
mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE);
mBottomPadding.setVisibility(View.GONE);
}
+ } else {
+ showDisconnectedProgressBar();
}
break;
}
@@ -316,7 +306,8 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
break;
}
default: // DISCONNECTED, FAILED
- if (mScreenState != SCREEN_STATE_CONNECTED) {
+ if (mScreenState != SCREEN_STATE_CONNECTED &&
+ mWifiSettings.getAccessPointsCount() > 0) {
showDisconnectedState(Summary.get(this, state));
}
break;
@@ -326,7 +317,8 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
private void showDisconnectedState(String stateString) {
showDisconnectedProgressBar();
- if (mScreenState == SCREEN_STATE_DISCONNECTED) {
+ if (mScreenState == SCREEN_STATE_DISCONNECTED &&
+ mWifiSettings.getAccessPointsCount() > 0) {
mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE);
mBottomPadding.setVisibility(View.GONE);
}
@@ -474,13 +466,11 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
parent.removeAllViews();
mWifiConfig = new WifiConfigUiForSetupWizardXL(this, parent, selectedAccessPoint, edit);
- // Tag will be updated in this method when needed.
- mConnectButton.setTag(null);
if (selectedAccessPoint == null) { // "Add network" flow
showAddNetworkTitle();
mConnectButton.setVisibility(View.VISIBLE);
- mConnectButton.setTag(CONNECT_BUTTON_TAG_ADD_NETWORK);
+ showDisconnectedProgressBar();
showEditingButtonState();
} else if (selectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
mNetworkName = selectedAccessPoint.getTitle().toString();
@@ -490,6 +480,7 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
} else {
mNetworkName = selectedAccessPoint.getTitle().toString();
showEditingTitle();
+ showDisconnectedProgressBar();
showEditingButtonState();
if (selectedAccessPoint.security == AccessPoint.SECURITY_EAP) {
onEapNetworkSelected();
@@ -645,8 +636,9 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
mAddNetworkButton.setEnabled(true);
mRefreshButton.setEnabled(true);
mSkipOrNextButton.setEnabled(true);
- mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE);
showDisconnectedProgressBar();
+ mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE);
+ mBottomPadding.setVisibility(View.GONE);
}
setPaddingVisibility(View.VISIBLE);
@@ -671,9 +663,10 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
/**
* Called when the list of AccessPoints are modified and this Activity needs to refresh
* the list.
+ * @param preferenceScreen
*/
/* package */ void onAccessPointsUpdated(
- PreferenceCategory holder, Collection<AccessPoint> accessPoints) {
+ PreferenceScreen preferenceScreen, Collection<AccessPoint> accessPoints) {
// If we already show some of access points but the bar still shows "scanning" state, it
// should be stopped.
if (mProgressBar.isIndeterminate() && accessPoints.size() > 0) {
@@ -688,20 +681,12 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
for (AccessPoint accessPoint : accessPoints) {
accessPoint.setLayoutResource(R.layout.custom_preference);
- holder.addPreference(accessPoint);
+ preferenceScreen.addPreference(accessPoint);
}
}
private void refreshAccessPoints(boolean disconnectNetwork) {
- final Object tag = mConnectButton.getTag();
- if (tag != null && (tag instanceof Integer) &&
- ((Integer)tag == CONNECT_BUTTON_TAG_ADD_NETWORK)) {
- // In "Add network" flow, we won't get DetaledState available for changing ProgressBar
- // state. Instead we manually show previous status here.
- showDisconnectedState(Summary.get(this, mPreviousNetworkState));
- } else {
- showScanningState();
- }
+ showScanningState();
if (disconnectNetwork) {
mWifiManager.disconnect();
@@ -801,11 +786,6 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis
mWifiManager.connectNetwork(config);
}
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- super.onCreateContextMenu(menu, view, menuInfo);
- }
-
/**
* Replace the current background with a new background whose id is resId if needed.
*/
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pDialog.java b/src/com/android/settings/wifi/p2p/WifiP2pDialog.java
new file mode 100644
index 0000000..380fa13
--- /dev/null
+++ b/src/com/android/settings/wifi/p2p/WifiP2pDialog.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.p2p;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.wifi.WpsConfiguration;
+import android.net.wifi.WpsConfiguration.Setup;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+/**
+ * Dialog to setup a p2p connection
+ */
+public class WifiP2pDialog extends AlertDialog implements AdapterView.OnItemSelectedListener {
+
+ static final int BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE;
+
+ private final DialogInterface.OnClickListener mListener;
+
+ private View mView;
+ private TextView mDeviceName;
+ private TextView mDeviceAddress;
+
+ /* These values come from "wifi_p2p_wps_setup" resource array */
+ private static final int WPS_PBC = 0;
+ private static final int WPS_KEYPAD = 1;
+ private static final int WPS_DISPLAY = 2;
+
+ private int mWpsSetupIndex = WPS_PBC; //default is pbc
+
+ WifiP2pDevice mDevice;
+
+ public WifiP2pDialog(Context context, DialogInterface.OnClickListener listener,
+ WifiP2pDevice device) {
+ super(context);
+ mListener = listener;
+ mDevice = device;
+ }
+
+ public WifiP2pConfig getConfig() {
+ WifiP2pConfig config = new WifiP2pConfig();
+ config.deviceAddress = mDeviceAddress.getText().toString();
+ config.deviceName = mDeviceName.getText().toString();
+ config.wpsConfig = new WpsConfiguration();
+ switch (mWpsSetupIndex) {
+ case WPS_PBC:
+ config.wpsConfig.setup = Setup.PBC;
+ break;
+ case WPS_KEYPAD:
+ config.wpsConfig.setup = Setup.KEYPAD;
+ config.wpsConfig.pin = ((TextView) mView.findViewById(R.id.wps_pin)).
+ getText().toString();
+ break;
+ case WPS_DISPLAY:
+ config.wpsConfig.setup = Setup.DISPLAY;
+ break;
+ default:
+ config.wpsConfig.setup = Setup.PBC;
+ break;
+ }
+ if (mDevice.isGroupOwner()) {
+ config.joinExistingGroup = true;
+ }
+ return config;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ mView = getLayoutInflater().inflate(R.layout.wifi_p2p_dialog, null);
+ Spinner mWpsSetup = ((Spinner) mView.findViewById(R.id.wps_setup));
+
+ setView(mView);
+ setInverseBackgroundForced(true);
+
+ Context context = getContext();
+
+ setTitle(R.string.wifi_p2p_settings_title);
+ mDeviceName = (TextView) mView.findViewById(R.id.device_name);
+ mDeviceAddress = (TextView) mView.findViewById(R.id.device_address);
+
+ setButton(BUTTON_SUBMIT, context.getString(R.string.wifi_connect), mListener);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(R.string.wifi_cancel), mListener);
+
+ if (mDevice != null) {
+ mDeviceName.setText(mDevice.deviceName);
+ mDeviceAddress.setText(mDevice.deviceAddress);
+ mWpsSetup.setSelection(mWpsSetupIndex); //keep pbc as default
+ }
+
+ mWpsSetup.setOnItemSelectedListener(this);
+
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mWpsSetupIndex = position;
+
+ if (mWpsSetupIndex == WPS_KEYPAD) {
+ mView.findViewById(R.id.wps_pin_entry).setVisibility(View.VISIBLE);
+ } else {
+ mView.findViewById(R.id.wps_pin_entry).setVisibility(View.GONE);
+ }
+ return;
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+
+}
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pEnabler.java b/src/com/android/settings/wifi/p2p/WifiP2pEnabler.java
new file mode 100644
index 0000000..fd79a58
--- /dev/null
+++ b/src/com/android/settings/wifi/p2p/WifiP2pEnabler.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.p2p;
+
+import com.android.settings.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.provider.Settings;
+import android.util.Log;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+/**
+ * WifiP2pEnabler is a helper to manage the Wifi p2p on/off
+ */
+public class WifiP2pEnabler implements CompoundButton.OnCheckedChangeListener {
+ private static final String TAG = "WifiP2pEnabler";
+
+ private final Context mContext;
+ private Switch mSwitch;
+ private int mWifiP2pState;
+ private final IntentFilter mIntentFilter;
+ private final Handler mHandler = new WifiP2pHandler();
+ private WifiP2pManager mWifiP2pManager;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
+ handleP2pStateChanged(intent.getIntExtra(
+ WifiP2pManager.EXTRA_WIFI_STATE, WifiP2pManager.WIFI_P2P_STATE_DISABLED));
+ }
+ }
+ };
+
+ public WifiP2pEnabler(Context context, Switch switch_) {
+ mContext = context;
+ mSwitch = switch_;
+
+ mWifiP2pManager = (WifiP2pManager) context.getSystemService(Context.WIFI_P2P_SERVICE);
+ if (!mWifiP2pManager.connectHandler(mContext, mHandler)) {
+ //Failure to set up connection
+ Log.e(TAG, "Failed to set up connection with wifi p2p service");
+ mWifiP2pManager = null;
+ mSwitch.setEnabled(false);
+ }
+ mIntentFilter = new IntentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+
+ }
+
+ public void resume() {
+ if (mWifiP2pManager == null) return;
+ mContext.registerReceiver(mReceiver, mIntentFilter);
+ mSwitch.setOnCheckedChangeListener(this);
+ }
+
+ public void pause() {
+ if (mWifiP2pManager == null) return;
+ mContext.unregisterReceiver(mReceiver);
+ mSwitch.setOnCheckedChangeListener(null);
+ }
+
+ public void setSwitch(Switch switch_) {
+ if (mSwitch == switch_) return;
+ mSwitch.setOnCheckedChangeListener(null);
+ mSwitch = switch_;
+ mSwitch.setOnCheckedChangeListener(this);
+
+ mSwitch.setChecked(mWifiP2pState == WifiP2pManager.WIFI_P2P_STATE_ENABLED);
+ }
+
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+
+ if (mWifiP2pManager == null) return;
+
+ if (isChecked) {
+ mWifiP2pManager.enableP2p();
+ } else {
+ mWifiP2pManager.disableP2p();
+ }
+ }
+
+ private void handleP2pStateChanged(int state) {
+ mSwitch.setEnabled(true);
+ switch (state) {
+ case WifiP2pManager.WIFI_P2P_STATE_ENABLED:
+ mWifiP2pState = WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+ mSwitch.setChecked(true);
+ break;
+ case WifiP2pManager.WIFI_P2P_STATE_DISABLED:
+ mWifiP2pState = WifiP2pManager.WIFI_P2P_STATE_DISABLED;
+ mSwitch.setChecked(false);
+ break;
+ default:
+ mWifiP2pState = WifiP2pManager.WIFI_P2P_STATE_DISABLED;
+ Log.e(TAG,"Unhandled wifi state " + state);
+ break;
+ }
+ }
+
+ private class WifiP2pHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case WifiP2pManager.HANDLER_DISCONNECTION:
+ //Failure to set up connection
+ Log.e(TAG, "Lost connection with wifi p2p service");
+ mWifiP2pManager = null;
+ mSwitch.setEnabled(false);
+ break;
+ case WifiP2pManager.ENABLE_P2P_FAILED:
+ mSwitch.setEnabled(true);
+ break;
+ default:
+ //Ignore
+ break;
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pPeer.java b/src/com/android/settings/wifi/p2p/WifiP2pPeer.java
new file mode 100644
index 0000000..35ae15a
--- /dev/null
+++ b/src/com/android/settings/wifi/p2p/WifiP2pPeer.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.p2p;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDevice.Status;
+import android.preference.Preference;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.ImageView;
+
+import java.util.Comparator;
+
+public class WifiP2pPeer extends Preference {
+
+ private static final int[] STATE_SECURED = {R.attr.state_encrypted};
+ public WifiP2pDevice device;
+
+ private int mRssi;
+ private ImageView mSignal;
+
+ private static final int SIGNAL_LEVELS = 4;
+
+ public WifiP2pPeer(Context context, WifiP2pDevice dev) {
+ super(context);
+ device = dev;
+ setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
+ mRssi = 60; //TODO: fix
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ if (TextUtils.isEmpty(device.deviceName)) {
+ setTitle(device.deviceAddress);
+ } else {
+ setTitle(device.deviceName);
+ }
+ mSignal = (ImageView) view.findViewById(R.id.signal);
+ if (mRssi == Integer.MAX_VALUE) {
+ mSignal.setImageDrawable(null);
+ } else {
+ mSignal.setImageResource(R.drawable.wifi_signal);
+ mSignal.setImageState(STATE_SECURED, true);
+ }
+ refresh();
+ super.onBindView(view);
+ }
+
+ @Override
+ public int compareTo(Preference preference) {
+ if (!(preference instanceof WifiP2pPeer)) {
+ return 1;
+ }
+ WifiP2pPeer other = (WifiP2pPeer) preference;
+
+ // devices go in the order of the status
+ if (device.status != other.device.status) {
+ return device.status.ordinal() < other.device.status.ordinal() ? -1 : 1;
+ }
+
+ // Sort by name/address
+ if (device.deviceName != null) {
+ return device.deviceName.compareToIgnoreCase(other.device.deviceName);
+ }
+
+ return device.deviceAddress.compareToIgnoreCase(other.device.deviceAddress);
+ }
+
+ int getLevel() {
+ if (mRssi == Integer.MAX_VALUE) {
+ return -1;
+ }
+ return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+ }
+
+ private void refresh() {
+ if (mSignal == null) {
+ return;
+ }
+ Context context = getContext();
+ mSignal.setImageLevel(getLevel());
+ String[] statusArray = context.getResources().getStringArray(R.array.wifi_p2p_status);
+ setSummary(statusArray[device.status.ordinal()]);
+ }
+}
diff --git a/src/com/android/settings/wifi/p2p/WifiP2pSettings.java b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
new file mode 100644
index 0000000..6ec3a1a
--- /dev/null
+++ b/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi.p2p;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Switch;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Collection;
+
+/*
+ * Displays Wi-fi p2p settings UI
+ */
+public class WifiP2pSettings extends SettingsPreferenceFragment {
+
+ private static final String TAG = "WifiP2pSettings";
+ private static final int MENU_ID_SEARCH = Menu.FIRST;
+ private static final int MENU_ID_CREATE_GROUP = Menu.FIRST + 1;
+ private static final int MENU_ID_ADVANCED = Menu.FIRST +2;
+
+
+ private final IntentFilter mIntentFilter = new IntentFilter();
+ private final Handler mHandler = new WifiP2pHandler();
+ private WifiP2pManager mWifiP2pManager;
+ private WifiP2pEnabler mWifiP2pEnabler;
+ private WifiP2pDialog mConnectDialog;
+ private OnClickListener mConnectListener;
+ private OnClickListener mDisconnectListener;
+ private WifiP2pPeer mSelectedWifiPeer;
+
+ private static final int DIALOG_CONNECT = 1;
+ private static final int DIALOG_DISCONNECT = 2;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
+ //TODO: nothing right now
+ } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
+ if (mWifiP2pManager != null) mWifiP2pManager.requestPeers();
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ addPreferencesFromResource(R.xml.wifi_p2p_settings);
+
+ mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+
+ final Activity activity = getActivity();
+ mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
+ if (!mWifiP2pManager.connectHandler(activity, mHandler)) {
+ //Failure to set up connection
+ Log.e(TAG, "Failed to set up connection with wifi p2p service");
+ mWifiP2pManager = null;
+ }
+
+ Switch actionBarSwitch = new Switch(activity);
+
+ if (activity instanceof PreferenceActivity) {
+ PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
+ if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
+ final int padding = activity.getResources().getDimensionPixelSize(
+ R.dimen.action_bar_switch_padding);
+ actionBarSwitch.setPadding(0, 0, padding, 0);
+ activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM);
+ activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ ActionBar.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_VERTICAL | Gravity.RIGHT));
+ }
+ }
+
+ mWifiP2pEnabler = new WifiP2pEnabler(activity, actionBarSwitch);
+
+ //connect dialog listener
+ mConnectListener = new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ WifiP2pConfig config = mConnectDialog.getConfig();
+ if (mWifiP2pManager != null) {
+ mWifiP2pManager.connect(config);
+ }
+ }
+ }
+ };
+
+ //disconnect dialog listener
+ mDisconnectListener = new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ if (mWifiP2pManager != null) {
+ mWifiP2pManager.disconnect();
+ }
+ }
+ }
+ };
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getActivity().registerReceiver(mReceiver, mIntentFilter);
+ if (mWifiP2pEnabler != null) {
+ mWifiP2pEnabler.resume();
+ }
+ if (mWifiP2pManager != null) mWifiP2pManager.discoverPeers();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mWifiP2pEnabler != null) {
+ mWifiP2pEnabler.pause();
+ }
+ getActivity().unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(Menu.NONE, MENU_ID_SEARCH, 0, R.string.wifi_p2p_menu_search)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, MENU_ID_CREATE_GROUP, 0, R.string.wifi_p2p_menu_create_group)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_p2p_menu_advanced)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ID_SEARCH:
+ mWifiP2pManager.discoverPeers();
+ return true;
+ case MENU_ID_CREATE_GROUP:
+ mWifiP2pManager.createGroup();
+ return true;
+ case MENU_ID_ADVANCED:
+ //TODO: add advanced settings for p2p
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
+ if (preference instanceof WifiP2pPeer) {
+ mSelectedWifiPeer = (WifiP2pPeer) preference;
+ if (mSelectedWifiPeer.device.status == WifiP2pDevice.Status.CONNECTED) {
+ showDialog(DIALOG_DISCONNECT);
+ } else {
+ showDialog(DIALOG_CONNECT);
+ }
+ }
+ return super.onPreferenceTreeClick(screen, preference);
+ }
+
+ @Override
+ public Dialog onCreateDialog(int id) {
+ if (id == DIALOG_CONNECT) {
+ mConnectDialog = new WifiP2pDialog(getActivity(), mConnectListener,
+ mSelectedWifiPeer.device);
+ return mConnectDialog;
+ } else if (id == DIALOG_DISCONNECT) {
+ AlertDialog dialog = new AlertDialog.Builder(getActivity())
+ .setTitle("Disconnect ?")
+ .setMessage("Do you want to disconnect ?")
+ .setPositiveButton(getActivity().getString(R.string.dlg_ok), mDisconnectListener)
+ .setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
+ .create();
+ return dialog;
+ }
+ return null;
+ }
+
+ private void updatePeers(WifiP2pDeviceList peers) {
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ preferenceScreen.removeAll();
+
+ for (WifiP2pDevice peer: peers.getDeviceList()) {
+ preferenceScreen.addPreference(new WifiP2pPeer(getActivity(), peer));
+ }
+ }
+
+ private class WifiP2pHandler extends Handler {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case WifiP2pManager.HANDLER_DISCONNECTION:
+ //Failure to set up connection
+ Log.e(TAG, "Lost connection with wifi p2p service");
+ mWifiP2pManager = null;
+ break;
+ case WifiP2pManager.RESPONSE_PEERS:
+ updatePeers(mWifiP2pManager.peersInResponse(message));
+ break;
+ default:
+ //Ignore
+ break;
+ }
+ }
+ }
+
+}