summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/settings/AccessibilitySettings.java106
-rw-r--r--src/com/android/settings/AccessibilityTutorialActivity.java616
-rw-r--r--src/com/android/settings/ApplicationSettings.java80
-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/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.java1578
-rw-r--r--src/com/android/settings/DateTimeSettings.java3
-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.java257
-rw-r--r--src/com/android/settings/DeviceInfoSettings.java21
-rw-r--r--src/com/android/settings/DisplaySettings.java156
-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.java193
-rw-r--r--src/com/android/settings/PointerSpeedPreference.java4
-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/SecuritySettings.java200
-rw-r--r--src/com/android/settings/Settings.java345
-rw-r--r--src/com/android/settings/SettingsCheckBoxPreference.java85
-rw-r--r--src/com/android/settings/SettingsLicenseActivity.java3
-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/TextToSpeechSettings.java642
-rw-r--r--src/com/android/settings/TrustedCredentialsSettings.java417
-rw-r--r--src/com/android/settings/UserDictionarySettings.java95
-rw-r--r--src/com/android/settings/Utils.java22
-rw-r--r--src/com/android/settings/WallpaperTypeSettings.java59
-rw-r--r--src/com/android/settings/WirelessSettings.java42
-rw-r--r--src/com/android/settings/accounts/AccountPreferenceBase.java14
-rw-r--r--src/com/android/settings/accounts/AccountSyncSettings.java28
-rw-r--r--src/com/android/settings/accounts/ChooseAccountActivity.java10
-rw-r--r--src/com/android/settings/accounts/ManageAccountsSettings.java1
-rw-r--r--src/com/android/settings/applications/ApplicationsState.java36
-rw-r--r--src/com/android/settings/applications/InstalledAppDetails.java25
-rw-r--r--src/com/android/settings/applications/ManageApplications.java41
-rw-r--r--src/com/android/settings/bluetooth/AdvancedBluetoothSettings.java96
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDeviceFilter.java10
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDevicePreference.java25
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEnabler.java84
-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/BluetoothSettings.java290
-rw-r--r--src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java45
-rw-r--r--src/com/android/settings/bluetooth/DevicePickerFragment.java4
-rw-r--r--src/com/android/settings/bluetooth/DeviceProfilesSettings.java4
-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/StorageMeasurement.java36
-rw-r--r--src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java14
-rw-r--r--src/com/android/settings/deviceinfo/UsageBarPreference.java6
-rw-r--r--src/com/android/settings/deviceinfo/UsbSettings.java150
-rw-r--r--src/com/android/settings/fuelgauge/BatteryHistoryChart.java17
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java207
-rw-r--r--src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java26
-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.java242
-rw-r--r--src/com/android/settings/inputmethod/UserDictionaryList.java109
-rw-r--r--src/com/android/settings/net/NetworkPolicyEditor.java172
-rw-r--r--src/com/android/settings/net/SummaryForAllUidLoader.java80
-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.java327
-rw-r--r--src/com/android/settings/vpn2/VpnProfile.java118
-rw-r--r--src/com/android/settings/vpn2/VpnSettings.java473
-rw-r--r--src/com/android/settings/widget/ChartAxis.java38
-rw-r--r--src/com/android/settings/widget/ChartGridView.java102
-rw-r--r--src/com/android/settings/widget/ChartNetworkSeriesView.java222
-rw-r--r--src/com/android/settings/widget/ChartSweepView.java437
-rw-r--r--src/com/android/settings/widget/ChartView.java119
-rw-r--r--src/com/android/settings/widget/DataUsageChartView.java406
-rw-r--r--src/com/android/settings/widget/InvertedChartAxis.java67
-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.java26
-rw-r--r--src/com/android/settings/wifi/WifiEnabler.java103
-rw-r--r--src/com/android/settings/wifi/WifiSettings.java251
-rw-r--r--src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java64
100 files changed, 9762 insertions, 4270 deletions
diff --git a/src/com/android/settings/AccessibilitySettings.java b/src/com/android/settings/AccessibilitySettings.java
index 826410d..bbb3678 100644
--- a/src/com/android/settings/AccessibilitySettings.java
+++ b/src/com/android/settings/AccessibilitySettings.java
@@ -16,14 +16,18 @@
package com.android.settings;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Service;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
@@ -50,7 +54,12 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
private static final String DEFAULT_SCREENREADER_MARKET_LINK =
"market://search?q=pname:com.google.android.marvin.talkback";
- private final String TOGGLE_ACCESSIBILITY_CHECKBOX =
+ private static final float LARGE_FONT_SCALE = 1.3f;
+
+ private static final String TOGGLE_LARGE_TEXT_CHECKBOX =
+ "toggle_large_text_checkbox";
+
+ private static final String TOGGLE_ACCESSIBILITY_CHECKBOX =
"toggle_accessibility_service_checkbox";
private static final String ACCESSIBILITY_SERVICES_CATEGORY =
@@ -65,10 +74,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
private static final String POWER_BUTTON_ENDS_CALL_CHECKBOX =
"power_button_ends_call";
- private final String KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX =
+ private static final String KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX =
"key_toggle_accessibility_service_checkbox";
- private final String KEY_LONG_PRESS_TIMEOUT_LIST_PREFERENCE =
+ private static final String KEY_LONG_PRESS_TIMEOUT_LIST_PREFERENCE =
"long_press_timeout_list_preference";
private static final int DIALOG_ID_DISABLE_ACCESSIBILITY = 1;
@@ -76,9 +85,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
private static final int DIALOG_ID_ENABLE_ACCESSIBILITY_SERVICE = 3;
private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 4;
+ private CheckBoxPreference mToggleLargeTextCheckBox;
private CheckBoxPreference mToggleAccessibilityCheckBox;
private CheckBoxPreference mToggleScriptInjectionCheckBox;
- private CheckBoxPreference mToggleAccessibilityServiceCheckBox;
+ private SettingsCheckBoxPreference mToggleAccessibilityServiceCheckBox;
private PreferenceCategory mPowerButtonCategory;
private CheckBoxPreference mPowerButtonEndsCallCheckBox;
@@ -87,8 +97,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
private ListPreference mLongPressTimeoutListPreference;
- private Map<String, ServiceInfo> mAccessibilityServices =
- new LinkedHashMap<String, ServiceInfo>();
+ private final Configuration mCurConfig = new Configuration();
+
+ private Map<String, AccessibilityServiceInfo> mAccessibilityServices =
+ new LinkedHashMap<String, AccessibilityServiceInfo>();
private TextUtils.SimpleStringSplitter mStringColonSplitter =
new TextUtils.SimpleStringSplitter(':');
@@ -99,6 +111,9 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
addPreferencesFromResource(R.xml.accessibility_settings);
+ mToggleLargeTextCheckBox = (CheckBoxPreference) findPreference(
+ TOGGLE_LARGE_TEXT_CHECKBOX);
+
mAccessibilityServicesCategory =
(PreferenceGroup) findPreference(ACCESSIBILITY_SERVICES_CATEGORY);
@@ -144,7 +159,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
@Override
public void onActivityCreated(Bundle savedInstanceState) {
- addAccessibilitServicePreferences();
+ addAccessibilityServicePreferences();
final HashSet<String> enabled = new HashSet<String>();
String settingValue = Settings.Secure.getString(getContentResolver(),
@@ -157,7 +172,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
}
}
- Map<String, ServiceInfo> accessibilityServices = mAccessibilityServices;
+ Map<String, AccessibilityServiceInfo> accessibilityServices = mAccessibilityServices;
for (String key : accessibilityServices.keySet()) {
CheckBoxPreference preference = (CheckBoxPreference) findPreference(key);
@@ -191,6 +206,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
}
+ readFontSizePreference();
+
super.onActivityCreated(savedInstanceState);
}
@@ -230,9 +247,9 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
throw new IllegalArgumentException(
KEY_TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX
+ " must be mapped to an instance of a "
- + CheckBoxPreference.class.getName());
+ + SettingsCheckBoxPreference.class.getName());
}
- mToggleAccessibilityServiceCheckBox = (CheckBoxPreference) preference;
+ mToggleAccessibilityServiceCheckBox = (SettingsCheckBoxPreference) preference;
}
}
@@ -250,10 +267,10 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
int count = mAccessibilityServicesCategory.getPreferenceCount();
for (int i = 0; i < count; i++) {
Preference pref = mAccessibilityServicesCategory.getPreference(i);
- pref.setEnabled(isEnabled);
+ if (pref != mToggleAccessibilityCheckBox) {
+ pref.setEnabled(isEnabled);
+ }
}
-
- mToggleScriptInjectionCheckBox.setEnabled(isEnabled);
}
@Override
@@ -262,6 +279,13 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
if (TOGGLE_ACCESSIBILITY_CHECKBOX.equals(key)) {
handleEnableAccessibilityStateChange((CheckBoxPreference) preference);
+ } else if (TOGGLE_LARGE_TEXT_CHECKBOX.equals(key)) {
+ try {
+ mCurConfig.fontScale = mToggleLargeTextCheckBox.isChecked()
+ ? LARGE_FONT_SCALE : 1;
+ ActivityManagerNative.getDefault().updateConfiguration(mCurConfig);
+ } catch (RemoteException e) {
+ }
} 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
@@ -274,7 +298,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
} else if (TOGGLE_ACCESSIBILITY_SCRIPT_INJECTION_CHECKBOX.equals(key)) {
handleToggleAccessibilityScriptInjection((CheckBoxPreference) preference);
} else if (preference instanceof CheckBoxPreference) {
- handleEnableAccessibilityServiceStateChange((CheckBoxPreference) preference);
+ handleEnableAccessibilityServiceStateChange((SettingsCheckBoxPreference) preference);
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
@@ -318,7 +342,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
*
* @param preference The preference.
*/
- private void handleEnableAccessibilityServiceStateChange(CheckBoxPreference preference) {
+ private void handleEnableAccessibilityServiceStateChange(
+ SettingsCheckBoxPreference preference) {
if (preference.isChecked()) {
mToggleAccessibilityServiceCheckBox = preference;
// set right enabled state since the user may press back
@@ -353,32 +378,56 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
/**
* Adds {@link CheckBoxPreference} for enabling or disabling an accessibility services.
*/
- private void addAccessibilitServicePreferences() {
+ private void addAccessibilityServicePreferences() {
AccessibilityManager accessibilityManager =
(AccessibilityManager) getSystemService(Service.ACCESSIBILITY_SERVICE);
- List<ServiceInfo> installedServices = accessibilityManager.getAccessibilityServiceList();
+ List<AccessibilityServiceInfo> installedServices =
+ accessibilityManager.getInstalledAccessibilityServiceList();
- if (installedServices.isEmpty()) {
- getPreferenceScreen().removePreference(mAccessibilityServicesCategory);
- return;
+ for (int i = 0; i < mAccessibilityServicesCategory.getPreferenceCount(); i++) {
+ Preference pref = mAccessibilityServicesCategory.getPreference(i);
+ if (pref != mToggleAccessibilityCheckBox
+ && pref != mToggleScriptInjectionCheckBox) {
+ mAccessibilityServicesCategory.removePreference(pref);
+ i--;
+ }
}
- getPreferenceScreen().addPreference(mAccessibilityServicesCategory);
-
for (int i = 0, count = installedServices.size(); i < count; ++i) {
- ServiceInfo serviceInfo = installedServices.get(i);
- String key = serviceInfo.packageName + "/" + serviceInfo.name;
-
- if (mAccessibilityServices.put(key, serviceInfo) == null) {
- CheckBoxPreference preference = new CheckBoxPreference(getActivity());
+ AccessibilityServiceInfo accessibilityServiceInfo = installedServices.get(i);
+ String key = accessibilityServiceInfo.getId();
+
+ if (mAccessibilityServices.put(key, accessibilityServiceInfo) == null) {
+ String settingsActivityName = accessibilityServiceInfo.getSettingsActivityName();
+ Intent settingsIntent = null;
+ if (!TextUtils.isEmpty(settingsActivityName)) {
+ String packageName = accessibilityServiceInfo.getResolveInfo()
+ .serviceInfo.packageName;
+ settingsIntent = new Intent(Intent.ACTION_MAIN);
+ settingsIntent.setClassName(packageName, settingsActivityName);
+ }
+ SettingsCheckBoxPreference preference = new SettingsCheckBoxPreference(
+ getActivity(), settingsIntent);
preference.setKey(key);
+ preference.setOrder(i);
+ ServiceInfo serviceInfo = accessibilityServiceInfo.getResolveInfo().serviceInfo;
preference.setTitle(serviceInfo.loadLabel(getActivity().getPackageManager()));
mAccessibilityServicesCategory.addPreference(preference);
}
}
}
+ public void readFontSizePreference() {
+ try {
+ mCurConfig.updateFrom(
+ ActivityManagerNative.getDefault().getConfiguration());
+ } catch (RemoteException e) {
+ }
+ mToggleLargeTextCheckBox.setChecked(Float.compare(mCurConfig.fontScale,
+ LARGE_FONT_SCALE) == 0);
+ }
+
@Override
public Dialog onCreateDialog(int dialogId) {
switch (dialogId) {
@@ -424,7 +473,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
.setMessage(getResources().getString(
R.string.accessibility_service_security_warning,
mAccessibilityServices.get(mToggleAccessibilityServiceCheckBox.getKey())
- .applicationInfo.loadLabel(getActivity().getPackageManager())))
+ .getResolveInfo().serviceInfo.applicationInfo.loadLabel(
+ getActivity().getPackageManager())))
.setCancelable(true)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
diff --git a/src/com/android/settings/AccessibilityTutorialActivity.java b/src/com/android/settings/AccessibilityTutorialActivity.java
new file mode 100644
index 0000000..be9b90d
--- /dev/null
+++ b/src/com/android/settings/AccessibilityTutorialActivity.java
@@ -0,0 +1,616 @@
+/*
+ * 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.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.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 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 = "com.android.settings.touchtutorial.LAUNCH_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;
+
+ 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 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);
+
+ setPreviousVisible(false);
+ }
+
+ @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);
+ }
+ }
+
+ @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);
+
+ setNextVisible(false);
+ setFinishVisible(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);
+ } 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 TextView mTitle;
+ private final Button mPrevious;
+ private final Button mNext;
+ private final Button mFinish;
+
+ /** 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;
+
+ final View container = LayoutInflater.from(context).inflate(
+ R.layout.accessibility_tutorial_container, this, true);
+
+ mInstructions = (TextView) container.findViewById(R.id.instructions);
+ mTitle = (TextView) container.findViewById(R.id.title);
+ mTitle.setText(titleResId);
+ mPrevious = (Button) container.findViewById(R.id.back_button);
+ mPrevious.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 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);
+ mTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+ 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.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();
+
+ protected void setFinishVisible(boolean visible) {
+ mFinish.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ /**
+ * 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 setNextVisible(boolean visible) {
+ mNext.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ protected void setPreviousVisible(boolean visible) {
+ mPrevious.setVisibility(visible ? VISIBLE : GONE);
+ }
+ }
+}
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/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java
index 9bbb66a..df50ada 100644
--- a/src/com/android/settings/BrightnessPreference.java
+++ b/src/com/android/settings/BrightnessPreference.java
@@ -26,7 +26,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.preference.SeekBarPreference;
+import android.preference.SeekBarDialogPreference;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.AttributeSet;
@@ -35,7 +35,7 @@ import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.SeekBar;
-public class BrightnessPreference extends SeekBarPreference implements
+public class BrightnessPreference extends SeekBarDialogPreference implements
SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener {
private SeekBar mSeekBar;
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
index 118bc6f..8311c4a 100644
--- a/src/com/android/settings/ChooseLockGeneric.java
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -27,6 +27,7 @@ import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
+import android.security.KeyStore;
public class ChooseLockGeneric extends PreferenceActivity {
@@ -48,9 +49,11 @@ public class ChooseLockGeneric extends PreferenceActivity {
private static final int CONFIRM_EXISTING_REQUEST = 100;
private static final String PASSWORD_CONFIRMED = "password_confirmed";
private static final String CONFIRM_CREDENTIALS = "confirm_credentials";
+ public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private DevicePolicyManager mDPM;
+ private KeyStore mKeyStore;
private boolean mPasswordConfirmed = false;
@Override
@@ -58,6 +61,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
super.onCreate(savedInstanceState);
mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+ mKeyStore = KeyStore.getInstance();
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
if (savedInstanceState != null) {
@@ -126,8 +130,8 @@ public class ChooseLockGeneric extends PreferenceActivity {
.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);
if (quality == -1) {
// If caller didn't specify password quality, show UI and allow the user to choose.
- quality = mDPM.getPasswordQuality(null);
- quality = upgradeQualityForEncryption(quality);
+ quality = getActivity().getIntent().getIntExtra(MINIMUM_QUALITY_KEY, -1);
+ quality = upgradeQuality(quality);
final PreferenceScreen prefScreen = getPreferenceScreen();
if (prefScreen != null) {
prefScreen.removeAll();
@@ -135,11 +139,26 @@ public class ChooseLockGeneric extends PreferenceActivity {
addPreferencesFromResource(R.xml.security_settings_picker);
disableUnusablePreferences(quality);
} else {
- quality = upgradeQualityForEncryption(quality);
updateUnlockMethodAndFinish(quality, false);
}
}
+ private int upgradeQuality(int quality) {
+ quality = upgradeQualityForDPM(quality);
+ quality = upgradeQualityForEncryption(quality);
+ quality = upgradeQualityForKeyStore(quality);
+ return quality;
+ }
+
+ private int upgradeQualityForDPM(int quality) {
+ // Compare min allowed password quality
+ int minQuality = mDPM.getPasswordQuality(null);
+ if (quality < minQuality) {
+ quality = minQuality;
+ }
+ return quality;
+ }
+
/**
* Mix in "encryption minimums" to any given quality value. This prevents users
* from downgrading the pattern/pin/password to a level below the minimums.
@@ -152,8 +171,17 @@ public class ChooseLockGeneric extends PreferenceActivity {
boolean encrypted = (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE)
|| (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING);
if (encrypted) {
- if (quality < DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ if (quality < CryptKeeperSettings.MIN_PASSWORD_QUALITY) {
+ quality = CryptKeeperSettings.MIN_PASSWORD_QUALITY;
+ }
+ }
+ return quality;
+ }
+
+ private int upgradeQualityForKeyStore(int quality) {
+ if (!mKeyStore.isEmpty()) {
+ if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
+ quality = CredentialStorage.MIN_PASSWORD_QUALITY;
}
}
return quality;
@@ -208,13 +236,7 @@ public class ChooseLockGeneric extends PreferenceActivity {
throw new IllegalStateException("Tried to update password without confirming it");
}
- // Compare min allowed password quality and launch appropriate security setting method
- int minQuality = mDPM.getPasswordQuality(null);
- if (quality < minQuality) {
- quality = minQuality;
- }
- quality = upgradeQualityForEncryption(quality);
-
+ quality = upgradeQuality(quality);
if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
int minLength = mDPM.getPasswordMinimumLength(null);
if (minLength < MIN_PASSWORD_LENGTH) {
diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java
index a0f2346..96255eb 100644
--- a/src/com/android/settings/ChooseLockPassword.java
+++ b/src/com/android/settings/ChooseLockPassword.java
@@ -405,8 +405,10 @@ public class ChooseLockPassword extends PreferenceActivity {
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN) {
+ // Check if this was the result of hitting the enter or "done" key
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
handleNext();
return true;
}
diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java
index d31fe3b..a069712 100644
--- a/src/com/android/settings/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/ChooseLockSettingsHelper.java
@@ -23,7 +23,10 @@ import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
-public class ChooseLockSettingsHelper {
+public final class ChooseLockSettingsHelper {
+
+ static final String EXTRA_KEY_PASSWORD = "password";
+
private LockPatternUtils mLockPatternUtils;
private Activity mActivity;
private Fragment mFragment;
@@ -49,8 +52,7 @@ public class ChooseLockSettingsHelper {
* @return true if one exists and we launched an activity to confirm it
* @see #onActivityResult(int, int, android.content.Intent)
*/
- protected boolean launchConfirmationActivity(int request,
- CharSequence message, CharSequence details) {
+ boolean launchConfirmationActivity(int request, CharSequence message, CharSequence details) {
boolean launched = false;
switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java
index 3f4a4f3..1229046 100644
--- a/src/com/android/settings/ConfirmLockPassword.java
+++ b/src/com/android/settings/ConfirmLockPassword.java
@@ -27,13 +27,16 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceActivity;
+import android.text.Editable;
import android.text.InputType;
+import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -58,7 +61,7 @@ public class ConfirmLockPassword extends PreferenceActivity {
}
public static class ConfirmLockPasswordFragment extends Fragment implements OnClickListener,
- OnEditorActionListener {
+ OnEditorActionListener, TextWatcher {
private static final long ERROR_MESSAGE_TIMEOUT = 3000;
private TextView mPasswordEntry;
private LockPatternUtils mLockPatternUtils;
@@ -66,6 +69,7 @@ public class ConfirmLockPassword extends PreferenceActivity {
private Handler mHandler = new Handler();
private PasswordEntryKeyboardHelper mKeyboardHelper;
private PasswordEntryKeyboardView mKeyboardView;
+ private Button mContinueButton;
// required constructor for fragments
@@ -87,9 +91,14 @@ public class ConfirmLockPassword extends PreferenceActivity {
// Disable IME on our window since we provide our own keyboard
view.findViewById(R.id.cancel_button).setOnClickListener(this);
- view.findViewById(R.id.next_button).setOnClickListener(this);
+ mContinueButton = (Button) view.findViewById(R.id.next_button);
+ mContinueButton.setOnClickListener(this);
+ mContinueButton.setEnabled(false); // disable until the user enters at least one char
+
mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
mPasswordEntry.setOnEditorActionListener(this);
+ mPasswordEntry.addTextChangedListener(this);
+
mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
mHeaderText = (TextView) view.findViewById(R.id.headerText);
final boolean isAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
@@ -140,7 +149,7 @@ public class ConfirmLockPassword extends PreferenceActivity {
if (mLockPatternUtils.checkPassword(pin)) {
Intent intent = new Intent();
- intent.putExtra("password", pin);
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
getActivity().setResult(RESULT_OK, intent);
getActivity().finish();
@@ -172,13 +181,27 @@ public class ConfirmLockPassword extends PreferenceActivity {
}, ERROR_MESSAGE_TIMEOUT);
}
+ // {@link OnEditorActionListener} methods.
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- if (actionId == EditorInfo.IME_NULL) {
+ // Check if this was the result of hitting the enter or "done" key
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
handleNext();
return true;
}
return false;
}
+
+ // {@link TextWatcher} methods.
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ public void afterTextChanged(Editable s) {
+ mContinueButton.setEnabled(mPasswordEntry.getText().length() > 0);
+ }
}
}
diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
index 0653d3f..2892930 100644
--- a/src/com/android/settings/ConfirmLockPattern.java
+++ b/src/com/android/settings/ConfirmLockPattern.java
@@ -256,7 +256,12 @@ public class ConfirmLockPattern extends PreferenceActivity {
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
if (mLockPatternUtils.checkPattern(pattern)) {
- getActivity().setResult(Activity.RESULT_OK);
+
+ Intent intent = new Intent();
+ intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
+ LockPatternUtils.patternToString(pattern));
+
+ getActivity().setResult(Activity.RESULT_OK, intent);
getActivity().finish();
} else {
if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL &&
diff --git a/src/com/android/settings/CredentialStorage.java b/src/com/android/settings/CredentialStorage.java
index 9d5a603..e246fce 100644
--- a/src/com/android/settings/CredentialStorage.java
+++ b/src/com/android/settings/CredentialStorage.java
@@ -18,213 +18,417 @@ package com.android.settings;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.res.Resources;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.security.KeyChain.KeyChainConnection;
+import android.security.KeyChain;
import android.security.KeyStore;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.internal.widget.LockPatternUtils;
-import java.io.UnsupportedEncodingException;
+/**
+ * CredentialStorage handles KeyStore reset, unlock, and install.
+ *
+ * CredentialStorage has a pretty convoluted state machine to migrate
+ * from the old style separate keystore password to a new key guard
+ * based password, as well as to deal with setting up the key guard if
+ * necessary.
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: factory state
+ *
+ * KeyStore: UNINITALIZED
+ * KeyGuard: ON
+ * Action: confirm key guard
+ * Notes: user had key guard but no keystore and upgraded from pre-ICS
+ * OR user had key guard and pre-ICS keystore password which was then reset
+ *
+ * KeyStore: LOCKED
+ * KeyGuard: OFF/ON
+ * Action: old unlock dialog
+ * Notes: assume old password, need to use it to unlock.
+ * if unlock, ensure key guard before install.
+ * if reset, treat as UNINITALIZED/OFF
+ *
+ * KeyStore: UNLOCKED
+ * KeyGuard: OFF
+ * Action: set up key guard
+ * Notes: ensure key guard, then proceed
+ *
+ * KeyStore: UNLOCKED
+ * keyguard: ON
+ * Action: normal unlock/install
+ * Notes: this is the common case
+ */
+public final class CredentialStorage extends Activity {
-public class CredentialStorage extends Activity implements TextWatcher,
- DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+ private static final String TAG = "CredentialStorage";
public static final String ACTION_UNLOCK = "com.android.credentials.UNLOCK";
- public static final String ACTION_SET_PASSWORD = "com.android.credentials.SET_PASSWORD";
public static final String ACTION_INSTALL = "com.android.credentials.INSTALL";
public static final String ACTION_RESET = "com.android.credentials.RESET";
- private static final String TAG = "CredentialStorage";
+ // This is the minimum acceptable password quality. If the current password quality is
+ // lower than this, keystore should not be activated.
+ static final int MIN_PASSWORD_QUALITY = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- private KeyStore mKeyStore = KeyStore.getInstance();
- private boolean mSubmit = false;
- private Bundle mBundle;
+ private static final int CONFIRM_KEY_GUARD_REQUEST = 1;
- private TextView mOldPassword;
- private TextView mNewPassword;
- private TextView mConfirmPassword;
- private TextView mError;
- private Button mButton;
+ private final KeyStore mKeyStore = KeyStore.getInstance();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ /**
+ * When non-null, the bundle containing credentials to install.
+ */
+ private Bundle mInstallBundle;
+
+ /**
+ * After unsuccessful KeyStore.unlock, the number of unlock
+ * attempts remaining before the KeyStore will reset itself.
+ *
+ * Reset to -1 on successful unlock or reset.
+ */
+ private int mRetriesRemaining = -1;
+
+ @Override protected void onResume() {
+ super.onResume();
Intent intent = getIntent();
String action = intent.getAction();
- int state = mKeyStore.test();
if (ACTION_RESET.equals(action)) {
- showResetDialog();
- } else if (ACTION_SET_PASSWORD.equals(action)) {
- showPasswordDialog(state == KeyStore.UNINITIALIZED);
+ new ResetDialog();
} else {
if (ACTION_INSTALL.equals(action) &&
"com.android.certinstaller".equals(getCallingPackage())) {
- mBundle = intent.getExtras();
- }
- if (state == KeyStore.UNINITIALIZED) {
- showPasswordDialog(true);
- } else if (state == KeyStore.LOCKED) {
- showUnlockDialog();
- } else {
- install();
- finish();
+ mInstallBundle = intent.getExtras();
}
+ // ACTION_UNLOCK also handled here in addition to ACTION_INSTALL
+ handleUnlockOrInstall();
}
}
- private void install() {
- if (mBundle != null && !mBundle.isEmpty()) {
- try {
- for (String key : mBundle.keySet()) {
- byte[] value = mBundle.getByteArray(key);
- if (value != null && !mKeyStore.put(key.getBytes("UTF-8"), value)) {
- Log.e(TAG, "Failed to install " + key);
- return;
- }
+ /**
+ * Based on the current state of the KeyStore and key guard, try to
+ * make progress on unlocking or installing to the keystore.
+ */
+ private void handleUnlockOrInstall() {
+ // something already decided we are done, do not proceed
+ if (isFinishing()) {
+ return;
+ }
+ switch (mKeyStore.state()) {
+ case UNINITIALIZED: {
+ ensureKeyGuard();
+ return;
+ }
+ case LOCKED: {
+ new UnlockDialog();
+ return;
+ }
+ case UNLOCKED: {
+ if (!checkKeyGuardQuality()) {
+ new ConfigureKeyGuardDialog();
+ return;
}
- setResult(RESULT_OK);
- } catch (UnsupportedEncodingException e) {
- // Should never happen.
- throw new RuntimeException(e);
+ installIfAvailable();
+ finish();
+ return;
}
}
}
- private void showResetDialog() {
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setTitle(android.R.string.dialog_alert_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.credentials_reset_hint)
- .setNeutralButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
+ /**
+ * Make sure the user enters the key guard to set or change the
+ * keystore password. This can be used in UNINITIALIZED to set the
+ * keystore password or UNLOCKED to change the password (as is the
+ * case after unlocking with an old-style password).
+ */
+ private void ensureKeyGuard() {
+ if (!checkKeyGuardQuality()) {
+ // key guard not setup, doing so will initialize keystore
+ new ConfigureKeyGuardDialog();
+ // will return to onResume after Activity
+ return;
+ }
+ // force key guard confirmation
+ if (confirmKeyGuard()) {
+ // will return password value via onActivityResult
+ return;
+ }
+ finish();
}
- private void showPasswordDialog(boolean firstTime) {
- View view = View.inflate(this, R.layout.credentials_dialog, null);
+ /**
+ * Returns true if the currently set key guard matches our minimum quality requirements.
+ */
+ private boolean checkKeyGuardQuality() {
+ int quality = new LockPatternUtils(this).getActivePasswordQuality();
+ return (quality >= MIN_PASSWORD_QUALITY);
+ }
- ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_password_hint);
- if (!firstTime) {
- view.findViewById(R.id.old_password_prompt).setVisibility(View.VISIBLE);
- mOldPassword = (TextView) view.findViewById(R.id.old_password);
- mOldPassword.setVisibility(View.VISIBLE);
- mOldPassword.addTextChangedListener(this);
+ /**
+ * Install credentials if available, otherwise do nothing.
+ */
+ private void installIfAvailable() {
+ if (mInstallBundle != null && !mInstallBundle.isEmpty()) {
+ Bundle bundle = mInstallBundle;
+ mInstallBundle = null;
+ for (String key : bundle.keySet()) {
+ byte[] value = bundle.getByteArray(key);
+ if (value != null && !mKeyStore.put(key, value)) {
+ Log.e(TAG, "Failed to install " + key);
+ return;
+ }
+ }
+ setResult(RESULT_OK);
}
- view.findViewById(R.id.new_passwords).setVisibility(View.VISIBLE);
- mNewPassword = (TextView) view.findViewById(R.id.new_password);
- mNewPassword.addTextChangedListener(this);
- mConfirmPassword = (TextView) view.findViewById(R.id.confirm_password);
- mConfirmPassword.addTextChangedListener(this);
- mError = (TextView) view.findViewById(R.id.error);
-
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setView(view)
- .setTitle(R.string.credentials_set_password)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
- mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- mButton.setEnabled(false);
}
- private void showUnlockDialog() {
- View view = View.inflate(this, R.layout.credentials_dialog, null);
-
- ((TextView) view.findViewById(R.id.hint)).setText(R.string.credentials_unlock_hint);
- mOldPassword = (TextView) view.findViewById(R.id.old_password);
- mOldPassword.setVisibility(View.VISIBLE);
- mOldPassword.addTextChangedListener(this);
- mError = (TextView) view.findViewById(R.id.error);
-
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setView(view)
- .setTitle(R.string.credentials_unlock)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
- dialog.setOnDismissListener(this);
- dialog.show();
- mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- mButton.setEnabled(false);
- }
+ /**
+ * Prompt for reset confirmation, resetting on confirmation, finishing otherwise.
+ */
+ private class ResetDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mResetConfirmed;
- public void afterTextChanged(Editable editable) {
- if ((mOldPassword == null || mOldPassword.getText().length() > 0) &&
- (mNewPassword == null || mNewPassword.getText().length() >= 8) &&
- (mConfirmPassword == null || mConfirmPassword.getText().length() >= 8)) {
- mButton.setEnabled(true);
- } else {
- mButton.setEnabled(false);
+ private ResetDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.credentials_reset_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
}
- }
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mResetConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
- public void onTextChanged(CharSequence s,int start, int before, int count) {
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mResetConfirmed) {
+ mResetConfirmed = false;
+ new ResetKeyStoreAndKeyChain().execute();
+ return;
+ }
+ finish();
+ }
}
- public void onClick(DialogInterface dialog, int button) {
- mSubmit = (button == DialogInterface.BUTTON_POSITIVE);
- if (button == DialogInterface.BUTTON_NEUTRAL) {
+ /**
+ * Background task to handle reset of both keystore and user installed CAs.
+ */
+ private class ResetKeyStoreAndKeyChain extends AsyncTask<Void, Void, Boolean> {
+
+ @Override protected Boolean doInBackground(Void... unused) {
+
mKeyStore.reset();
- Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show();
+
+ try {
+ KeyChainConnection keyChainConnection = KeyChain.bind(CredentialStorage.this);
+ try {
+ return keyChainConnection.getService().reset();
+ } catch (RemoteException e) {
+ return false;
+ } finally {
+ keyChainConnection.close();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ @Override protected void onPostExecute(Boolean success) {
+ if (success) {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_not_erased, Toast.LENGTH_SHORT).show();
+ }
+ finish();
}
}
- public void onDismiss(DialogInterface dialog) {
- if (mSubmit) {
- mSubmit = false;
- mError.setVisibility(View.VISIBLE);
+ /**
+ * Prompt for key guard configuration confirmation.
+ */
+ private class ConfigureKeyGuardDialog
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mConfigureConfirmed;
- if (mNewPassword == null) {
- mKeyStore.unlock(mOldPassword.getText().toString());
- } else {
- String newPassword = mNewPassword.getText().toString();
- String confirmPassword = mConfirmPassword.getText().toString();
+ private ConfigureKeyGuardDialog() {
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.credentials_configure_lock_screen_hint)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mConfigureConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
- if (!newPassword.equals(confirmPassword)) {
- mError.setText(R.string.credentials_passwords_mismatch);
- ((AlertDialog) dialog).show();
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mConfigureConfirmed) {
+ mConfigureConfirmed = false;
+ Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
+ MIN_PASSWORD_QUALITY);
+ startActivity(intent);
+ return;
+ }
+ finish();
+ }
+ }
+
+ /**
+ * Confirm existing key guard, returning password via onActivityResult.
+ */
+ private boolean confirmKeyGuard() {
+ Resources res = getResources();
+ boolean launched = new ChooseLockSettingsHelper(this)
+ .launchConfirmationActivity(CONFIRM_KEY_GUARD_REQUEST,
+ res.getText(R.string.master_clear_gesture_prompt),
+ res.getText(R.string.master_clear_gesture_explanation));
+ return launched;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ /**
+ * Receive key guard password initiated by confirmKeyGuard.
+ */
+ if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
+ if (resultCode == Activity.RESULT_OK) {
+ String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+ if (!TextUtils.isEmpty(password)) {
+ // success
+ mKeyStore.password(password);
+ // return to onResume
return;
- } else if (mOldPassword == null) {
- mKeyStore.password(newPassword);
- } else {
- mKeyStore.password(mOldPassword.getText().toString(), newPassword);
}
}
+ // failed confirmation, bail
+ finish();
+ }
+ }
+
+ /**
+ * Prompt for unlock with old-style password.
+ *
+ * On successful unlock, ensure migration to key guard before continuing.
+ * On unsuccessful unlock, retry by calling handleUnlockOrInstall.
+ */
+ private class UnlockDialog implements TextWatcher,
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener
+ {
+ private boolean mUnlockConfirmed;
+
+ private final Button mButton;
+ private final TextView mOldPassword;
+ private final TextView mError;
+
+ private UnlockDialog() {
+ View view = View.inflate(CredentialStorage.this, R.layout.credentials_dialog, null);
+
+ CharSequence text;
+ if (mRetriesRemaining == -1) {
+ text = getResources().getText(R.string.credentials_unlock_hint);
+ } else if (mRetriesRemaining > 3) {
+ text = getResources().getText(R.string.credentials_wrong_password);
+ } else if (mRetriesRemaining == 1) {
+ text = getResources().getText(R.string.credentials_reset_warning);
+ } else {
+ text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
+ }
- int error = mKeyStore.getLastError();
- if (error == KeyStore.NO_ERROR) {
- Toast.makeText(this, R.string.credentials_enabled, Toast.LENGTH_SHORT).show();
- install();
- } else if (error == KeyStore.UNINITIALIZED) {
- Toast.makeText(this, R.string.credentials_erased, Toast.LENGTH_SHORT).show();
- } else if (error >= KeyStore.WRONG_PASSWORD) {
- int count = error - KeyStore.WRONG_PASSWORD + 1;
- if (count > 3) {
- mError.setText(R.string.credentials_wrong_password);
- } else if (count == 1) {
- mError.setText(R.string.credentials_reset_warning);
- } else {
- mError.setText(getString(R.string.credentials_reset_warning_plural, count));
+ ((TextView) view.findViewById(R.id.hint)).setText(text);
+ mOldPassword = (TextView) view.findViewById(R.id.old_password);
+ mOldPassword.setVisibility(View.VISIBLE);
+ mOldPassword.addTextChangedListener(this);
+ mError = (TextView) view.findViewById(R.id.error);
+
+ AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
+ .setView(view)
+ .setTitle(R.string.credentials_unlock)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .create();
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ mButton.setEnabled(false);
+ }
+
+ @Override public void afterTextChanged(Editable editable) {
+ mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
+ }
+
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override public void onTextChanged(CharSequence s,int start, int before, int count) {
+ }
+
+ @Override public void onClick(DialogInterface dialog, int button) {
+ mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
+ }
+
+ @Override public void onDismiss(DialogInterface dialog) {
+ if (mUnlockConfirmed) {
+ mUnlockConfirmed = false;
+ mError.setVisibility(View.VISIBLE);
+ mKeyStore.unlock(mOldPassword.getText().toString());
+ int error = mKeyStore.getLastError();
+ if (error == KeyStore.NO_ERROR) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_enabled,
+ Toast.LENGTH_SHORT).show();
+ // aha, now we are unlocked, switch to key guard.
+ // we'll end up back in onResume to install
+ ensureKeyGuard();
+ } else if (error == KeyStore.UNINITIALIZED) {
+ mRetriesRemaining = -1;
+ Toast.makeText(CredentialStorage.this,
+ R.string.credentials_erased,
+ Toast.LENGTH_SHORT).show();
+ // we are reset, we can now set new password with key guard
+ handleUnlockOrInstall();
+ } else if (error >= KeyStore.WRONG_PASSWORD) {
+ // we need to try again
+ mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
+ handleUnlockOrInstall();
}
- ((AlertDialog) dialog).show();
return;
}
+ finish();
}
- finish();
}
}
diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java
index edf00d5..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..739ed58
--- /dev/null
+++ b/src/com/android/settings/DataUsageSummary.java
@@ -0,0 +1,1578 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkPolicyManager.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.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.view.ViewGroup.LayoutParams.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.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.RemoteException;
+import android.os.ServiceManager;
+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.text.format.Time;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.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.LinearLayout;
+import android.widget.ListView;
+import android.widget.NumberPicker;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TabHost;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabContentFactory;
+import android.widget.TabHost.TabSpec;
+import android.widget.TabWidget;
+import android.widget.TextView;
+
+import com.android.internal.telephony.Phone;
+import com.android.settings.net.NetworkPolicyEditor;
+import com.android.settings.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;
+
+/**
+ * Panel show data usage history across various networks, including options to
+ * inspect based on usage cycle and control through {@link NetworkPolicy}.
+ */
+public class DataUsageSummary extends Fragment {
+ private static final String TAG = "DataUsage";
+ private static final boolean LOGD = true;
+
+ private static final String TAB_3G = "3g";
+ private static final String TAB_4G = "4g";
+ private static final String TAB_MOBILE = "mobile";
+ private static final String TAB_WIFI = "wifi";
+
+ private static final String TAG_CONFIRM_ROAMING = "confirmRoaming";
+ private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
+ private static final String TAG_CYCLE_EDITOR = "cycleEditor";
+ private static final String TAG_POLICY_LIMIT = "policyLimit";
+ private static final 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 INetworkStatsService mStatsService;
+ private INetworkPolicyManager mPolicyService;
+ private ConnectivityManager mConnService;
+
+ private static final String PREF_FILE = "data_usage";
+ private static final String PREF_SHOW_WIFI = "show_wifi";
+
+ private SharedPreferences mPrefs;
+
+ private TabHost mTabHost;
+ private 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 Spinner mCycleSpinner;
+ private CycleAdapter mCycleAdapter;
+
+ private DataUsageChartView mChart;
+
+ private View mAppDetail;
+ private TextView mAppTitle;
+ private TextView mAppSubtitle;
+ private Button mAppSettings;
+
+ private LinearLayout mAppSwitches;
+ private CheckBox mAppRestrict;
+ private View mAppRestrictView;
+
+ private boolean mShowWifi = 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);
+
+ mStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ mPolicyService = INetworkPolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ mConnService = (ConnectivityManager) getActivity().getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+
+ mPrefs = getActivity().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
+
+ mPolicyEditor = new NetworkPolicyEditor(mPolicyService);
+ mPolicyEditor.read();
+
+ mShowWifi = mPrefs.getBoolean(PREF_SHOW_WIFI, false);
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ final Context context = inflater.getContext();
+ final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
+
+ mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
+ 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
+ mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
+ 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 = view.findViewById(R.id.app_detail);
+ mAppTitle = (TextView) view.findViewById(R.id.app_title);
+ mAppSubtitle = (TextView) view.findViewById(R.id.app_subtitle);
+ mAppSwitches = (LinearLayout) view.findViewById(R.id.app_switches);
+
+ mAppSettings = (Button) view.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);
+ }
+
+ // only assign layout transitions once first layout is finished
+ mHeader.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();
+
+ // template and tab has been selected; show dialog if limit passed
+ final String action = intent.getAction();
+ if (ACTION_DATA_USAGE_LIMIT.equals(action)) {
+ PolicyLimitFragment.show(this);
+ }
+
+ // 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) {
+ 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);
+ showWifi.setVisible(hasMobileRadio(context) && hasWifiRadio(context));
+ showWifi.setChecked(mShowWifi);
+
+ }
+
+ @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;
+ }
+ }
+ 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() {
+ mHeader.getViewTreeObserver().removeGlobalOnLayoutListener(mFirstLayoutListener);
+
+ mTabsContainer.setLayoutTransition(new LayoutTransition());
+ mHeader.setLayoutTransition(new LayoutTransition());
+ mNetworkSwitchesContainer.setLayoutTransition(new LayoutTransition());
+
+ final LayoutTransition chartTransition = new LayoutTransition();
+ chartTransition.setStartDelay(LayoutTransition.APPEARING, 0);
+ chartTransition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
+ mChart.setLayoutTransition(chartTransition);
+ }
+ };
+
+ /**
+ * Rebuild all tabs based on {@link NetworkPolicyEditor} and
+ * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
+ * first tab, and kicks off a full rebind of body contents.
+ */
+ private void updateTabs() {
+ final Context context = getActivity();
+ mTabHost.clearAllTabs();
+
+ final boolean mobileSplit = isMobilePolicySplit();
+ if (mobileSplit && hasMobile4gRadio(context)) {
+ mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
+ mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
+ }
+
+ if (mShowWifi && hasWifiRadio(context) && hasMobileRadio(context)) {
+ if (!mobileSplit) {
+ mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
+ }
+ mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
+ }
+
+ final boolean hasTabs = mTabWidget.getTabCount() > 0;
+ mTabWidget.setVisibility(hasTabs ? View.VISIBLE : View.GONE);
+ if (hasTabs) {
+ if (mIntentTab != null) {
+ // select default tab, which will kick off updateBody()
+ mTabHost.setCurrentTabByTag(mIntentTab);
+ } else {
+ // select first tab, which will kick off updateBody()
+ mTabHost.setCurrentTab(0);
+ }
+ } else {
+ // no tabs visible; update body manually
+ updateBody();
+ }
+ }
+
+ /**
+ * Factory that provide empty {@link View} to make {@link TabHost} happy.
+ */
+ private TabContentFactory mEmptyTabContent = new TabContentFactory() {
+ /** {@inheritDoc} */
+ public View createTabContent(String tag) {
+ return new View(mTabHost.getContext());
+ }
+ };
+
+ /**
+ * Build {@link TabSpec} with thin indicator, and empty content.
+ */
+ private TabSpec buildTabSpec(String tag, int titleRes) {
+ final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
+ final View indicator = inflater.inflate(
+ R.layout.tab_indicator_thin_holo, mTabWidget, false);
+ final TextView title = (TextView) indicator.findViewById(android.R.id.title);
+ title.setText(titleRes);
+ return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
+ }
+
+ private OnTabChangeListener mTabListener = new OnTabChangeListener() {
+ /** {@inheritDoc} */
+ public void onTabChanged(String tabId) {
+ // user changed tab; update body
+ updateBody();
+ }
+ };
+
+ /**
+ * Update body content based on current tab. Loads
+ * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
+ * binds them to visible controls.
+ */
+ private void updateBody() {
+ mBinding = true;
+
+ final Context context = getActivity();
+ final String tabTag = mTabHost.getCurrentTabTag();
+
+ final String currentTab;
+ if (tabTag != null) {
+ currentTab = tabTag;
+ } else if (hasMobileRadio(context)) {
+ currentTab = TAB_MOBILE;
+ } else if (hasWifiRadio(context)) {
+ currentTab = TAB_WIFI;
+ } else {
+ throw new IllegalStateException("no mobile or wifi radios");
+ }
+
+ 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 = new NetworkTemplate(MATCH_WIFI, null);
+
+ } else {
+ // make sure we show for non-wifi
+ mDataEnabledView.setVisibility(View.VISIBLE);
+ mDisableAtLimitView.setVisibility(View.VISIBLE);
+ }
+
+ final String subscriberId = getActiveSubscriberId(context);
+ if (TAB_MOBILE.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_mobile);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_mobile_limit);
+ mDataEnabled.setChecked(mConnService.getMobileDataEnabled());
+ mTemplate = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId);
+
+ } else if (TAB_3G.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_3g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_3g_limit);
+ // TODO: bind mDataEnabled to 3G radio state
+ mTemplate = new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId);
+
+ } else if (TAB_4G.equals(currentTab)) {
+ setPreferenceTitle(mDataEnabledView, R.string.data_usage_enable_4g);
+ setPreferenceTitle(mDisableAtLimitView, R.string.data_usage_disable_4g_limit);
+ // TODO: bind mDataEnabled to 4G radio state
+ mTemplate = new NetworkTemplate(MATCH_MOBILE_4G, subscriberId);
+ }
+
+ try {
+ // load stats for current template
+ mHistory = mStatsService.getHistoryForNetwork(mTemplate);
+ } catch (RemoteException e) {
+ // since we can't do much without policy or history, and we don't
+ // want to leave with half-baked UI, we bail hard.
+ throw new RuntimeException("problem reading network policy or stats", e);
+ }
+
+ // bind chart to historical stats
+ mChart.bindNetworkStats(mHistory);
+
+ // 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() {
+ 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);
+
+ final PackageManager pm = getActivity().getPackageManager();
+ mAppTitle.setText(pm.getNameForUid(mUid));
+
+ // enable settings button when package provides it
+ // TODO: target torwards entire UID instead of just first package
+ final String[] packageNames = pm.getPackagesForUid(mUid);
+ if (packageNames != null && packageNames.length > 0) {
+ mAppSettingsIntent = new Intent(Intent.ACTION_MANAGE_NETWORK_USAGE);
+ mAppSettingsIntent.setPackage(packageNames[0]);
+ mAppSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT);
+
+ final boolean matchFound = pm.resolveActivity(mAppSettingsIntent, 0) != null;
+ mAppSettings.setEnabled(matchFound);
+
+ } else {
+ mAppSettingsIntent = null;
+ mAppSettings.setEnabled(false);
+ }
+
+ try {
+ // load stats for current uid and template
+ // TODO: read template from extras
+ mDetailHistory = mStatsService.getHistoryForUid(mTemplate, mUid, NetworkStats.TAG_NONE);
+ } catch (RemoteException e) {
+ // since we can't do much without history, and we don't want to
+ // leave with half-baked UI, we bail hard.
+ throw new RuntimeException("problem reading network stats", e);
+ }
+
+ // bind chart to historical stats
+ mChart.bindDetailNetworkStats(mDetailHistory);
+
+ updateDetailData();
+
+ final Context context = getActivity();
+ if (NetworkPolicyManager.isUidValidForPolicy(context, mUid)) {
+ 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 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() {
+ return !mConnService.getBackgroundDataSetting();
+ }
+
+ private void setRestrictBackground(boolean restrictBackground) {
+ if (LOGD) Log.d(TAG, "setRestrictBackground()");
+ mConnService.setBackgroundDataSetting(!restrictBackground);
+ mMenuRestrictBackground.setChecked(restrictBackground);
+ }
+
+ 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, "setRestrictBackground()");
+ 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);
+
+ // reflect policy limit in checkbox
+ mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
+ mChart.bindNetworkPolicy(policy);
+
+ if (refreshCycle) {
+ // generate cycle list based on policy and available history
+ updateCycleList(policy);
+ }
+ }
+
+ /**
+ * 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(true);
+ }
+
+ 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);
+
+ switch (mTemplate.getMatchRule()) {
+ case MATCH_MOBILE_ALL: {
+ mConnService.setMobileDataEnabled(dataEnabled);
+ }
+ }
+ }
+ };
+
+ private View.OnClickListener mDisableAtLimitListener = new View.OnClickListener() {
+ /** {@inheritDoc} */
+ public void onClick(View v) {
+ final boolean disableAtLimit = !mDisableAtLimit.isChecked();
+ if (disableAtLimit) {
+ // enabling limit; show confirmation dialog which eventually
+ // calls setPolicyLimitBytes() once user confirms.
+ ConfirmLimitFragment.show(DataUsageSummary.this);
+ } else {
+ setPolicyLimitBytes(LIMIT_DISABLED);
+ }
+ }
+ };
+
+ private 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, mHistory.getEnd());
+
+ 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();
+
+ if (isAppDetailMode()) {
+ if (mDetailHistory != null) {
+ final Context context = mChart.getContext();
+ final long now = System.currentTimeMillis();
+ final NetworkStatsHistory.Entry entry = mDetailHistory.getValues(
+ start, end, now, null);
+ final long total = entry.rxBytes + entry.txBytes;
+ mAppSubtitle.setText(Formatter.formatFileSize(context, total));
+ }
+
+ getLoaderManager().destroyLoader(LOADER_SUMMARY);
+
+ } else {
+ // kick off loader for detailed stats
+ getLoaderManager().restartLoader(LOADER_SUMMARY,
+ SummaryForAllUidLoader.buildArgs(mTemplate, start, end), mSummaryForAllUid);
+
+ }
+ }
+
+ 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);
+ }
+
+ /** {@inheritDoc} */
+ public void onLoaderReset(Loader<NetworkStats> loader) {
+ mAdapter.bindStats(null);
+ }
+ };
+
+ private boolean isMobilePolicySplit() {
+ final String subscriberId = getActiveSubscriberId(getActivity());
+ return mPolicyEditor.isMobilePolicySplit(subscriberId);
+ }
+
+ private void setMobilePolicySplit(boolean split) {
+ final String subscriberId = getActiveSubscriberId(getActivity());
+ mPolicyEditor.setMobilePolicySplit(subscriberId, split);
+ }
+
+ private static String getActiveSubscriberId(Context context) {
+ final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ return telephony.getSubscriberId();
+ }
+
+ private DataUsageChartListener mChartListener = new DataUsageChartListener() {
+ /** {@inheritDoc} */
+ public void onInspectRangeChanged() {
+ if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
+ updateDetailData();
+ }
+
+ /** {@inheritDoc} */
+ public void onWarningChanged() {
+ setPolicyWarningBytes(mChart.getWarningBytes());
+ }
+
+ /** {@inheritDoc} */
+ public void onLimitChanged() {
+ setPolicyLimitBytes(mChart.getLimitBytes());
+ }
+ };
+
+
+ /**
+ * List item that reflects a specific data usage cycle.
+ */
+ public static class CycleItem {
+ public CharSequence label;
+ public long start;
+ public long end;
+
+ private static final StringBuilder sBuilder = new StringBuilder(50);
+ private static final java.util.Formatter sFormatter = new java.util.Formatter(
+ sBuilder, Locale.getDefault());
+
+ CycleItem(CharSequence label) {
+ this.label = label;
+ }
+
+ public CycleItem(Context context, long start, long end) {
+ this.label = formatDateRangeUtc(context, start, end);
+ this.start = start;
+ this.end = end;
+ }
+
+ private static String formatDateRangeUtc(Context context, long start, long end) {
+ synchronized (sBuilder) {
+ sBuilder.setLength(0);
+ return DateUtils.formatDateRange(context, sFormatter, start, end,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
+ Time.TIMEZONE_UTC).toString();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return label.toString();
+ }
+ }
+
+ /**
+ * Special-case data usage cycle that triggers dialog to change
+ * {@link NetworkPolicy#cycleDay}.
+ */
+ public static class CycleChangeItem extends CycleItem {
+ public CycleChangeItem(Context context) {
+ super(context.getString(R.string.data_usage_change_cycle));
+ }
+ }
+
+ public static class CycleAdapter extends ArrayAdapter<CycleItem> {
+ 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();
+
+ /**
+ * 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);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.data_usage_item, parent, false);
+ }
+
+ final Context context = parent.getContext();
+
+ final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
+ final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
+
+ final AppUsageItem item = mItems.get(position);
+ text1.setText(resolveLabelForUid(context, item.uid));
+ text2.setText(Formatter.formatFileSize(context, item.total));
+
+ return convertView;
+ }
+
+ }
+
+ /**
+ * Empty {@link Fragment} that controls display of UID details in
+ * {@link DataUsageSummary}.
+ */
+ public static class AppDetailsFragment extends Fragment {
+ public 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 {
+ public static final String EXTRA_MESSAGE_ID = "messageId";
+ public static final String EXTRA_LIMIT_BYTES = "limitBytes";
+
+ public static void show(DataUsageSummary parent) {
+ final Bundle args = new Bundle();
+
+ // TODO: customize default limits based on network template
+ switch (parent.mTemplate.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER: {
+ args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
+ args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+ break;
+ }
+ case MATCH_MOBILE_4G: {
+ args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
+ args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+ break;
+ }
+ case MATCH_MOBILE_ALL: {
+ args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
+ args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
+ break;
+ }
+ }
+
+ final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
+ final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.data_usage_limit_dialog_title);
+ builder.setMessage(messageId);
+
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setPolicyLimitBytes(limitBytes);
+ }
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog to edit {@link NetworkPolicy#cycleDay}.
+ */
+ public static class CycleEditorFragment extends DialogFragment {
+ public static final String EXTRA_CYCLE_DAY = "cycleDay";
+
+ public static void show(DataUsageSummary parent) {
+ final NetworkPolicy policy = parent.mPolicyEditor.getPolicy(parent.mTemplate);
+ final Bundle args = new Bundle();
+ args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
+
+ final CycleEditorFragment dialog = new CycleEditorFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
+
+ final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
+ final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
+
+ final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
+
+ cycleDayPicker.setMinValue(1);
+ cycleDayPicker.setMaxValue(31);
+ cycleDayPicker.setValue(oldCycleDay);
+ cycleDayPicker.setWrapSelectorWheel(true);
+
+ builder.setTitle(R.string.data_usage_cycle_editor_title);
+ builder.setView(view);
+
+ builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final int cycleDay = cycleDayPicker.getValue();
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ target.setPolicyCycleDay(cycleDay);
+ }
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * Dialog explaining that {@link NetworkPolicy#limitBytes} has been passed,
+ * and giving the user an option to bypass.
+ */
+ public static class PolicyLimitFragment extends DialogFragment {
+ public static final String EXTRA_TITLE_ID = "titleId";
+
+ public static void show(DataUsageSummary parent) {
+ final Bundle args = new Bundle();
+
+ switch (parent.mTemplate.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER: {
+ args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_3g_title);
+ break;
+ }
+ case MATCH_MOBILE_4G: {
+ args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_4g_title);
+ break;
+ }
+ case MATCH_MOBILE_ALL: {
+ args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_mobile_title);
+ break;
+ }
+ }
+
+ final PolicyLimitFragment dialog = new PolicyLimitFragment();
+ dialog.setArguments(args);
+ dialog.setTargetFragment(parent, 0);
+ dialog.show(parent.getFragmentManager(), TAG_POLICY_LIMIT);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Context context = getActivity();
+
+ final int titleId = getArguments().getInt(EXTRA_TITLE_ID);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(titleId);
+ builder.setMessage(R.string.data_usage_disabled_dialog);
+
+ builder.setPositiveButton(android.R.string.ok, null);
+ builder.setNegativeButton(R.string.data_usage_disabled_dialog_enable,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
+ if (target != null) {
+ // TODO: consider "allow 100mb more data", or
+ // only bypass limit for current cycle.
+ target.setPolicyLimitBytes(LIMIT_DISABLED);
+ }
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ /**
+ * 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 ConnectivityManager#setBackgroundDataSetting(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 int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, MATCH_MOBILE_ALL);
+ switch (networkTemplate) {
+ case MATCH_MOBILE_3G_LOWER:
+ return TAB_3G;
+ case MATCH_MOBILE_4G:
+ return TAB_4G;
+ case MATCH_MOBILE_ALL:
+ return TAB_MOBILE;
+ case MATCH_WIFI:
+ return TAB_WIFI;
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Resolve best descriptive label for the given UID.
+ */
+ public static CharSequence resolveLabelForUid(Context context, int uid) {
+ final Resources res = context.getResources();
+ final PackageManager pm = context.getPackageManager();
+
+ // handle special case labels
+ switch (uid) {
+ case android.os.Process.SYSTEM_UID:
+ return res.getText(R.string.process_kernel_label);
+ case TrafficStats.UID_REMOVED:
+ return res.getText(R.string.data_usage_uninstalled_apps);
+ }
+
+ // otherwise fall back to using packagemanager labels
+ final String[] packageNames = pm.getPackagesForUid(uid);
+ final int length = packageNames != null ? packageNames.length : 0;
+
+ CharSequence label = pm.getNameForUid(uid);
+ try {
+ if (length == 1) {
+ final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
+ label = info.loadLabel(pm);
+ } else if (length > 1) {
+ for (String packageName : packageNames) {
+ final PackageInfo info = pm.getPackageInfo(packageName, 0);
+ if (info.sharedUserLabel != 0) {
+ label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo);
+ if (!TextUtils.isEmpty(label)) {
+ break;
+ }
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ }
+
+ if (TextUtils.isEmpty(label)) {
+ label = Integer.toString(uid);
+ }
+ return label;
+ }
+
+ /**
+ * Test if device has a mobile data radio.
+ */
+ private static boolean hasMobileRadio(Context context) {
+ final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+
+ // mobile devices should have MOBILE network tracker regardless of
+ // connection status.
+ return conn.getNetworkInfo(TYPE_MOBILE) != null;
+ }
+
+ /**
+ * Test if device has a mobile 4G data radio.
+ */
+ private static boolean hasMobile4gRadio(Context context) {
+ final ConnectivityManager conn = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+
+ // WiMAX devices should have WiMAX network tracker regardless of
+ // connection status.
+ final boolean hasWimax = conn.getNetworkInfo(TYPE_WIMAX) != null;
+ final boolean hasLte = telephony.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE;
+ return hasWimax || hasLte;
+ }
+
+ /**
+ * Test if device has a Wi-Fi data radio.
+ */
+ private static boolean hasWifiRadio(Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
+ }
+
+ /**
+ * Inflate a {@link Preference} style layout, adding the given {@link View}
+ * widget into {@link android.R.id#widget_frame}.
+ */
+ private static View inflatePreference(LayoutInflater inflater, ViewGroup root, View widget) {
+ final View view = inflater.inflate(R.layout.preference, root, false);
+ final LinearLayout widgetFrame = (LinearLayout) view.findViewById(
+ android.R.id.widget_frame);
+ widgetFrame.addView(widget, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ return view;
+ }
+
+ /**
+ * Set {@link android.R.id#title} for a preference view inflated with
+ * {@link #inflatePreference(LayoutInflater, 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..31e6a33 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()));
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..10afd55 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,33 @@ 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 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;
+
// To track whether Yes was clicked in the adb warning dialog
private boolean mOkClicked;
@@ -58,12 +90,28 @@ 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);
+
removeHdcpOptionsForProduction();
}
@@ -89,6 +137,13 @@ 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();
}
private void updateHdcpValues() {
@@ -110,6 +165,169 @@ 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("android", "com.android.server.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);
+ } 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);
+ } catch (RemoteException e) {
+ }
+ }
+
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
@@ -142,11 +360,40 @@ 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();
}
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 +423,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/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
index cdb0147..6ab88d0 100644
--- a/src/com/android/settings/DisplaySettings.java
+++ b/src/com/android/settings/DisplaySettings.java
@@ -18,21 +18,21 @@ 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.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 +44,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 +65,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 +77,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 +150,33 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
screenTimeoutPreference.setEnabled(revisedEntries.size() > 0);
}
+ int floatToIndex(float val, int resid) {
+ String[] indices = getResources().getStringArray(resid);
+ float lastVal = Float.parseFloat(indices[0]);
+ for (int i=1; i<indices.length; i++) {
+ float thisVal = Float.parseFloat(indices[i]);
+ if (val < (lastVal + (thisVal-lastVal)*.5f)) {
+ return i-1;
+ }
+ lastVal = thisVal;
+ }
+ return indices.length-1;
+ }
+
+ public void readFontSizePreference(ListPreference pref) {
+ try {
+ mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to retrieve font size");
+ }
+ pref.setValueIndex(floatToIndex(mCurConfig.fontScale, R.array.entryvalues_font_size));
+ }
+
@Override
public void onResume() {
super.onResume();
- updateState(true);
+ updateState();
getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), true,
mAccelerometerRotationObserver);
@@ -152,33 +189,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 +200,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().updateConfiguration(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 +216,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..645e6d7
--- /dev/null
+++ b/src/com/android/settings/LocationSettings.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.location.LocationManager;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+
+import java.util.Observable;
+import java.util.Observer;
+
+/**
+ * Gesture lock pattern settings.
+ */
+public class LocationSettings extends SettingsPreferenceFragment
+ implements OnPreferenceChangeListener {
+
+ // Location Settings
+ private static final String KEY_LOCATION_NETWORK = "location_network";
+ private static final String KEY_LOCATION_GPS = "location_gps";
+ private static final String KEY_ASSISTED_GPS = "assisted_gps";
+ private static final String KEY_USE_LOCATION = "location_use_for_services";
+
+ private CheckBoxPreference mNetwork;
+ private CheckBoxPreference mGps;
+ private CheckBoxPreference mAssistedGps;
+ private CheckBoxPreference mUseLocation;
+
+ // These provide support for receiving notification when Location Manager settings change.
+ // This is necessary because the Network Location Provider can change settings
+ // if the user does not confirm enabling the provider.
+ private ContentQueryMap mContentQueryMap;
+
+ private Observer mSettingsObserver;
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // listen for Location Manager settings changes
+ Cursor settingsCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, null,
+ "(" + Settings.System.NAME + "=?)",
+ new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED},
+ null);
+ mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mSettingsObserver != null) {
+ mContentQueryMap.deleteObserver(mSettingsObserver);
+ }
+ }
+
+ private PreferenceScreen createPreferenceHierarchy() {
+ PreferenceScreen root = getPreferenceScreen();
+ if (root != null) {
+ root.removeAll();
+ }
+ addPreferencesFromResource(R.xml.location_settings);
+ root = getPreferenceScreen();
+
+ mNetwork = (CheckBoxPreference) root.findPreference(KEY_LOCATION_NETWORK);
+ mGps = (CheckBoxPreference) root.findPreference(KEY_LOCATION_GPS);
+ mAssistedGps = (CheckBoxPreference) root.findPreference(KEY_ASSISTED_GPS);
+ if (GoogleLocationSettingHelper.isAvailable(getActivity())) {
+ // GSF present, Add setting for 'Use My Location'
+ CheckBoxPreference useLocation = new CheckBoxPreference(getActivity());
+ useLocation.setKey(KEY_USE_LOCATION);
+ useLocation.setTitle(R.string.use_location_title);
+ useLocation.setSummaryOn(R.string.use_location_summary_enabled);
+ useLocation.setSummaryOff(R.string.use_location_summary_disabled);
+ useLocation.setChecked(
+ GoogleLocationSettingHelper.getUseLocationForServices(getActivity())
+ == GoogleLocationSettingHelper.USE_LOCATION_FOR_SERVICES_ON);
+ useLocation.setPersistent(false);
+ useLocation.setOnPreferenceChangeListener(this);
+ getPreferenceScreen().addPreference(useLocation);
+ mUseLocation = useLocation;
+ }
+
+ // Change the summary for wifi-only devices
+ if (Utils.isWifiOnly()) {
+ mNetwork.setSummaryOn(R.string.location_neighborhood_level_wifi);
+ }
+
+ return root;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Make sure we reload the preference hierarchy since some of these settings
+ // depend on others...
+ createPreferenceHierarchy();
+ updateLocationToggles();
+
+ if (mSettingsObserver == null) {
+ mSettingsObserver = new Observer() {
+ public void update(Observable o, Object arg) {
+ updateLocationToggles();
+ }
+ };
+ mContentQueryMap.addObserver(mSettingsObserver);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+
+ if (preference == mNetwork) {
+ Settings.Secure.setLocationProviderEnabled(getContentResolver(),
+ LocationManager.NETWORK_PROVIDER, mNetwork.isChecked());
+ } else if (preference == mGps) {
+ boolean enabled = mGps.isChecked();
+ Settings.Secure.setLocationProviderEnabled(getContentResolver(),
+ LocationManager.GPS_PROVIDER, enabled);
+ if (mAssistedGps != null) {
+ mAssistedGps.setEnabled(enabled);
+ }
+ } else if (preference == mAssistedGps) {
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED,
+ mAssistedGps.isChecked() ? 1 : 0);
+ } else {
+ // If we didn't handle it, let preferences handle it.
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ return true;
+ }
+
+ /*
+ * Creates toggles for each available location provider
+ */
+ private void updateLocationToggles() {
+ ContentResolver res = getContentResolver();
+ boolean gpsEnabled = Settings.Secure.isLocationProviderEnabled(
+ res, LocationManager.GPS_PROVIDER);
+ mNetwork.setChecked(Settings.Secure.isLocationProviderEnabled(
+ res, LocationManager.NETWORK_PROVIDER));
+ mGps.setChecked(gpsEnabled);
+ if (mAssistedGps != null) {
+ mAssistedGps.setChecked(Settings.Secure.getInt(res,
+ Settings.Secure.ASSISTED_GPS_ENABLED, 2) == 1);
+ mAssistedGps.setEnabled(gpsEnabled);
+ }
+ }
+
+ /**
+ * see confirmPatternThenDisableAndClear
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ createPreferenceHierarchy();
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ if (preference == mUseLocation) {
+ boolean newValue = (value == null ? false : (Boolean) value);
+ GoogleLocationSettingHelper.setUseLocationForServices(getActivity(), newValue);
+ // We don't want to change the value immediately here, since the user may click
+ // disagree in the dialog that pops up. When the activity we just launched exits, this
+ // activity will be restated and the new value re-read, so the checkbox will get its
+ // new value then.
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/PointerSpeedPreference.java b/src/com/android/settings/PointerSpeedPreference.java
index e9704fd..6a6a71f 100644
--- a/src/com/android/settings/PointerSpeedPreference.java
+++ b/src/com/android/settings/PointerSpeedPreference.java
@@ -25,7 +25,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.preference.SeekBarPreference;
+import android.preference.SeekBarDialogPreference;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.AttributeSet;
@@ -33,7 +33,7 @@ import android.view.IWindowManager;
import android.view.View;
import android.widget.SeekBar;
-public class PointerSpeedPreference extends SeekBarPreference implements
+public class PointerSpeedPreference extends SeekBarDialogPreference implements
SeekBar.OnSeekBarChangeListener {
private SeekBar mSeekBar;
diff --git a/src/com/android/settings/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/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index dc4c42b..057e5de 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(),
@@ -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/Settings.java b/src/com/android/settings/Settings.java
index 2532747..15366e8 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -17,17 +17,30 @@
package com.android.settings;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.preference.PreferenceActivity;
+import android.text.TextUtils;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.Switch;
+import android.widget.TextView;
+import com.android.settings.bluetooth.BluetoothEnabler;
+import com.android.settings.wifi.WifiEnabler;
+
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -36,15 +49,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 +74,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_SolidActionBar_SplitActionBarWhenNarrow);
+ setTheme(theme);
+
getMetaData();
mInLocalHeaderSwitch = true;
super.onCreate(savedInstanceState);
@@ -92,6 +113,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 +132,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 +193,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 +203,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 +219,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 +252,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 +274,35 @@ 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)) {
+ intent.putExtra(EXTRA_THEME, android.R.style.Theme_Holo_SolidActionBar);
+ }
+
+ intent.setClass(this, SubSettings.class);
+ return intent;
+ }
+
/**
* Populate the activity with the top-level headers.
*/
@Override
- public void onBuildHeaders(List<Header> target) {
- loadHeadersFromResource(R.xml.settings_headers, target);
+ public void onBuildHeaders(List<Header> headers) {
+ loadHeadersFromResource(R.xml.settings_headers, headers);
- updateHeaderList(target);
+ updateHeaderList(headers);
+
+ mHeaders = headers;
}
private void updateHeaderList(List<Header> target) {
@@ -250,14 +316,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 +364,7 @@ public class Settings extends PreferenceActivity implements ButtonBarHandler {
}
}
} catch (NameNotFoundException nnfe) {
+ // No recovery
}
}
@@ -300,38 +378,207 @@ 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 void setListAdapter(ListAdapter adapter) {
+ if (mHeaders == null) {
+ mHeaders = new ArrayList<Header>();
+ // When the saved state provides the list of headers, onBuildHeaders is not called
+ // Copy the list of Headers from the adapter, preserving their order
+ for (int i = 0; i < adapter.getCount(); i++) {
+ mHeaders.add((Header) adapter.getItem(i));
+ }
+ }
+
+ // Ignore the adapter provided by PreferenceActivity and substitute ours instead
+ super.setListAdapter(new HeaderAdapter(this, mHeaders));
+ }
+
/*
* Settings subclasses for launching independently.
*/
-
- public static class BluetoothSettingsActivity extends Settings { }
- public static class WirelessSettingsActivity extends Settings { }
- public static class TetherSettingsActivity extends Settings { }
- public static class VpnSettingsActivity extends Settings { }
- public static class DateTimeSettingsActivity extends Settings { }
- public static class StorageSettingsActivity extends Settings { }
- public static class WifiSettingsActivity extends Settings { }
- public static class InputMethodAndLanguageSettingsActivity extends Settings { }
- public static class InputMethodConfigActivity extends Settings { }
- public static class InputMethodAndSubtypeEnablerActivity extends Settings { }
- public static class LocalePickerActivity extends Settings { }
- public static class UserDictionarySettingsActivity extends Settings { }
- public static class SoundSettingsActivity extends Settings { }
- public static class DisplaySettingsActivity extends Settings { }
- public static class DeviceInfoSettingsActivity extends Settings { }
- public static class ApplicationSettingsActivity extends Settings { }
- public static class ManageApplicationsActivity extends Settings { }
- public static class StorageUseActivity extends Settings { }
- public static class DevelopmentSettingsActivity extends Settings { }
- public static class AccessibilitySettingsActivity extends Settings { }
- public static class SecuritySettingsActivity extends Settings { }
- public static class PrivacySettingsActivity extends Settings { }
- public static class DockSettingsActivity extends Settings { }
- public static class RunningServicesActivity extends Settings { }
- public static class ManageAccountsSettingsActivity extends Settings { }
- public static class PowerUsageSummaryActivity extends Settings { }
- public static class AccountSyncSettingsActivity extends Settings { }
- public static class AccountSyncSettingsInAddAccountActivity extends Settings { }
- public static class CryptKeeperSettingsActivity extends Settings { }
- public static class DeviceAdminSettingsActivity extends Settings { }
+ public static class BluetoothSettingsActivity extends Settings { /* empty */ }
+ public static class WirelessSettingsActivity extends Settings { /* empty */ }
+ public static class TetherSettingsActivity extends Settings { /* empty */ }
+ public static class VpnSettingsActivity extends Settings { /* empty */ }
+ public static class DateTimeSettingsActivity extends Settings { /* empty */ }
+ public static class StorageSettingsActivity extends Settings { /* empty */ }
+ public static class WifiSettingsActivity extends Settings { /* empty */ }
+ public static class InputMethodAndLanguageSettingsActivity extends Settings { /* empty */ }
+ public static class InputMethodAndSubtypeEnablerActivity extends Settings { /* empty */ }
+ public static class LocalePickerActivity extends Settings { /* empty */ }
+ public static class UserDictionarySettingsActivity extends Settings { /* empty */ }
+ public static class SoundSettingsActivity extends Settings { /* empty */ }
+ public static class DisplaySettingsActivity extends Settings { /* empty */ }
+ public static class DeviceInfoSettingsActivity extends Settings { /* empty */ }
+ public static class ApplicationSettingsActivity extends Settings { /* empty */ }
+ public static class ManageApplicationsActivity extends Settings { /* empty */ }
+ public static class StorageUseActivity extends Settings { /* empty */ }
+ public static class DevelopmentSettingsActivity extends Settings { /* empty */ }
+ public static class AccessibilitySettingsActivity extends Settings { /* empty */ }
+ public static class SecuritySettingsActivity extends Settings { /* empty */ }
+ public static class LocationSettingsActivity extends Settings { /* empty */ }
+ public static class PrivacySettingsActivity extends Settings { /* empty */ }
+ public static class DockSettingsActivity extends Settings { /* empty */ }
+ public static class RunningServicesActivity extends Settings { /* empty */ }
+ public static class ManageAccountsSettingsActivity extends Settings { /* empty */ }
+ public static class PowerUsageSummaryActivity extends Settings { /* empty */ }
+ public static class AccountSyncSettingsActivity extends Settings { /* empty */ }
+ public static class AccountSyncSettingsInAddAccountActivity extends Settings { /* empty */ }
+ public static class CryptKeeperSettingsActivity extends Settings { /* empty */ }
+ public static class DeviceAdminSettingsActivity extends Settings { /* empty */ }
+ public static class DataUsageSummaryActivity extends Settings { /* empty */ }
+ public static class AdvancedWifiSettingsActivity extends Settings { /* empty */ }
+ public static class AdvancedBluetoothSettingsActivity extends Settings { /* empty */ }
+ public static class TextToSpeechSettingsActivity extends Settings { /* empty */ }
}
diff --git a/src/com/android/settings/SettingsCheckBoxPreference.java b/src/com/android/settings/SettingsCheckBoxPreference.java
new file mode 100644
index 0000000..026e4e6
--- /dev/null
+++ b/src/com/android/settings/SettingsCheckBoxPreference.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.content.Context;
+import android.content.Intent;
+import android.preference.CheckBoxPreference;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+
+/**
+ * CheckBox preference that optionally shows an icon for launching a settings
+ * {@link android.app.Activity}. The settings activity, if intent for launching
+ * it was provided, can be stared only if the CheckBox in is checked.
+ */
+public class SettingsCheckBoxPreference extends CheckBoxPreference {
+
+ // Integer.MIN_VALUE means not initalized
+ private static int sDimAlpha = Integer.MIN_VALUE;
+
+ private final Intent mSettingsIntent;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context Context for accessing resources.
+ * @param settingsIntent Intent to use as settings for the item represented by
+ * this preference. Pass <code>null</code> if there is no associated
+ * settings activity.
+ */
+ public SettingsCheckBoxPreference(Context context, Intent settingsIntent) {
+ super(context);
+
+ if (sDimAlpha == Integer.MIN_VALUE) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
+ sDimAlpha = (int) (outValue.getFloat() * 255);
+ }
+
+ mSettingsIntent = settingsIntent;
+ setWidgetLayoutResource(R.layout.preference_settings_checkbox_widget);
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+ ImageView settingsButton = (ImageView) view.findViewById(R.id.settings_button);
+ if (mSettingsIntent != null) {
+ CheckBox checkbox = (CheckBox) view.findViewById(com.android.internal.R.id.checkbox);
+ if (checkbox.isChecked()) {
+ settingsButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View view) {
+ getContext().startActivity(mSettingsIntent);
+ }
+ });
+ }
+ settingsButton.setVisibility(View.VISIBLE);
+ if (checkbox.isChecked() && isEnabled()) {
+ settingsButton.setAlpha(255);
+ } else {
+ settingsButton.setAlpha(sDimAlpha);
+ }
+ } else {
+ settingsButton.setVisibility(View.GONE);
+ view.findViewById(R.id.divider).setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java
index 99828ce..2960180 100644
--- a/src/com/android/settings/SettingsLicenseActivity.java
+++ b/src/com/android/settings/SettingsLicenseActivity.java
@@ -21,7 +21,6 @@ import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -46,7 +45,7 @@ import java.util.zip.GZIPInputStream;
public class SettingsLicenseActivity extends Activity {
private static final String TAG = "SettingsLicenseActivity";
- private static final boolean LOGV = false || Config.LOGV;
+ private static final boolean LOGV = false || false;
private static final String DEFAULT_LICENSE_PATH = "/system/etc/NOTICE.html.gz";
private static final String PROPERTY_LICENSE_PATH = "ro.config.license_path";
diff --git a/src/com/android/settings/SubSettings.java b/src/com/android/settings/SubSettings.java
new file mode 100644
index 0000000..9cd3c31
--- /dev/null
+++ b/src/com/android/settings/SubSettings.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+/**
+ * Stub class for showing sub-settings; we can't use the main Settings class
+ * since for our app it is a special singleTask class.
+ */
+public class SubSettings extends Settings {
+}
diff --git a/src/com/android/settings/TestingSettingsBroadcastReceiver.java b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
index c6cd7e1..cea12c5 100644
--- a/src/com/android/settings/TestingSettingsBroadcastReceiver.java
+++ b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
@@ -6,7 +6,6 @@ import static android.provider.Telephony.Intents.SECRET_CODE_ACTION;
import android.content.Context;
import android.content.Intent;
import android.content.BroadcastReceiver;
-import android.util.Config;
import android.util.Log;
import android.view.KeyEvent;
diff --git a/src/com/android/settings/TextToSpeechSettings.java b/src/com/android/settings/TextToSpeechSettings.java
index 62edac9..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 b13126a..ea89a1b 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);
@@ -235,13 +275,28 @@ public class UserDictionarySettings extends ListFragment implements DialogCreata
// The user was editing a word, so do a delete/add
deleteWord(mDialogEditingWord);
}
-
+
// Disallow duplicates
deleteWord(word);
-
+
// TODO: present UI for picking whether to add word to all locales, or current.
- UserDictionary.Words.addWord(getActivity(), word.toString(),
- 250, UserDictionary.Words.LOCALE_TYPE_ALL);
+ if (null == mLocale) {
+ // Null means insert with the default system locale.
+ UserDictionary.Words.addWord(getActivity(), word.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT);
+ } else if ("".equals(mLocale)) {
+ // Empty string means insert for all languages.
+ UserDictionary.Words.addWord(getActivity(), word.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_ALL);
+ } else {
+ // TODO: fix the framework so that it can accept a locale when we add a word
+ // to the user dictionary instead of querying the system locale.
+ final Locale prevLocale = Locale.getDefault();
+ Locale.setDefault(Utils.createLocaleFromString(mLocale));
+ UserDictionary.Words.addWord(getActivity(), word.toString(),
+ FREQUENCY_FOR_USER_DICTIONARY_ADDS, UserDictionary.Words.LOCALE_TYPE_CURRENT);
+ Locale.setDefault(prevLocale);
+ }
if (!mCursor.requery()) {
throw new IllegalStateException("can't requery on already-closed cursor.");
}
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 18c6159..422ae90 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -36,8 +36,10 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import java.net.InetAddress;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
public class Utils {
@@ -308,4 +310,24 @@ public class Utils {
}
return addresses;
}
+
+ public static Locale createLocaleFromString(String localeStr) {
+ // TODO: is there a better way to actually construct a locale that will match?
+ // The main problem is, on top of Java specs, locale.toString() and
+ // new Locale(locale.toString()).toString() do not return equal() strings in
+ // many cases, because the constructor takes the only string as the language
+ // code. So : new Locale("en", "US").toString() => "en_US"
+ // And : new Locale("en_US").toString() => "en_us"
+ if (null == localeStr)
+ return Locale.getDefault();
+ String[] brokenDownLocale = localeStr.split("_", 3);
+ // split may not return a 0-length array.
+ if (1 == brokenDownLocale.length) {
+ return new Locale(brokenDownLocale[0]);
+ } else if (2 == brokenDownLocale.length) {
+ return new Locale(brokenDownLocale[0], brokenDownLocale[1]);
+ } else {
+ return new Locale(brokenDownLocale[0], brokenDownLocale[1], brokenDownLocale[2]);
+ }
+ }
}
diff --git a/src/com/android/settings/WallpaperTypeSettings.java b/src/com/android/settings/WallpaperTypeSettings.java
new file mode 100644
index 0000000..fa0b4e4
--- /dev/null
+++ b/src/com/android/settings/WallpaperTypeSettings.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.preference.Preference;
+
+import java.util.List;
+
+public class WallpaperTypeSettings extends SettingsPreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.wallpaper_settings);
+
+ populateWallpaperTypes();
+ }
+
+ private void populateWallpaperTypes() {
+ // Search for activities that satisfy the ACTION_SET_WALLPAPER action
+ Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ final PackageManager pm = getPackageManager();
+ List<ResolveInfo> rList = pm.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+
+ // Add Preference items for each of the matching activities
+ for (ResolveInfo info : rList) {
+ Preference pref = new Preference(getActivity());
+ Intent prefIntent = new Intent(intent);
+ prefIntent.setComponent(new ComponentName(
+ info.activityInfo.packageName, info.activityInfo.name));
+ pref.setIntent(prefIntent);
+ CharSequence label = info.loadLabel(pm);
+ if (label == null) label = info.activityInfo.packageName;
+ pref.setTitle(label);
+ getPreferenceScreen().addPreference(pref);
+ }
+ }
+}
diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java
index 2844f3b..d09fcc5 100644
--- a/src/com/android/settings/WirelessSettings.java
+++ b/src/com/android/settings/WirelessSettings.java
@@ -16,35 +16,27 @@
package com.android.settings;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.settings.bluetooth.BluetoothEnabler;
-import com.android.settings.wifi.WifiEnabler;
-import com.android.settings.nfc.NfcEnabler;
-
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
-import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.nfc.NfcAdapter;
import android.os.Bundle;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.provider.Settings;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.settings.nfc.NfcEnabler;
+
public class WirelessSettings extends SettingsPreferenceFragment {
private static final String KEY_TOGGLE_AIRPLANE = "toggle_airplane";
- private static final String KEY_TOGGLE_BLUETOOTH = "toggle_bluetooth";
- private static final String KEY_TOGGLE_WIFI = "toggle_wifi";
private static final String KEY_TOGGLE_NFC = "toggle_nfc";
- private static final String KEY_WIFI_SETTINGS = "wifi_settings";
- private static final String KEY_BT_SETTINGS = "bt_settings";
private static final String KEY_VPN_SETTINGS = "vpn_settings";
private static final String KEY_TETHER_SETTINGS = "tether_settings";
private static final String KEY_PROXY_SETTINGS = "proxy_settings";
@@ -55,9 +47,7 @@ public class WirelessSettings extends SettingsPreferenceFragment {
private AirplaneModeEnabler mAirplaneModeEnabler;
private CheckBoxPreference mAirplaneModePreference;
- private WifiEnabler mWifiEnabler;
private NfcEnabler mNfcEnabler;
- private BluetoothEnabler mBtEnabler;
/**
* Invoked on each preference click in this hierarchy, overrides
@@ -95,15 +85,10 @@ public class WirelessSettings extends SettingsPreferenceFragment {
addPreferencesFromResource(R.xml.wireless_settings);
final Activity activity = getActivity();
- CheckBoxPreference airplane = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
- CheckBoxPreference wifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI);
- CheckBoxPreference bt = (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH);
+ mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
CheckBoxPreference nfc = (CheckBoxPreference) findPreference(KEY_TOGGLE_NFC);
- mAirplaneModeEnabler = new AirplaneModeEnabler(activity, airplane);
- mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE);
- mWifiEnabler = new WifiEnabler(activity, wifi);
- mBtEnabler = new BluetoothEnabler(activity, bt);
+ mAirplaneModeEnabler = new AirplaneModeEnabler(activity, mAirplaneModePreference);
mNfcEnabler = new NfcEnabler(activity, nfc);
String toggleable = Settings.System.getString(activity.getContentResolver(),
@@ -111,21 +96,12 @@ public class WirelessSettings extends SettingsPreferenceFragment {
// Manually set dependencies for Wifi when not toggleable.
if (toggleable == null || !toggleable.contains(Settings.System.RADIO_WIFI)) {
- wifi.setDependency(KEY_TOGGLE_AIRPLANE);
- findPreference(KEY_WIFI_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE);
findPreference(KEY_VPN_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE);
}
// Manually set dependencies for Bluetooth when not toggleable.
if (toggleable == null || !toggleable.contains(Settings.System.RADIO_BLUETOOTH)) {
- bt.setDependency(KEY_TOGGLE_AIRPLANE);
- findPreference(KEY_BT_SETTINGS).setDependency(KEY_TOGGLE_AIRPLANE);
- }
-
- // Remove Bluetooth Settings if Bluetooth service is not available.
- if (ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE) == null) {
- getPreferenceScreen().removePreference(bt);
- getPreferenceScreen().removePreference(findPreference(KEY_BT_SETTINGS));
+ // No bluetooth-dependent items in the list. Code kept in case one is added later.
}
// Remove NFC if its not available
@@ -191,8 +167,6 @@ public class WirelessSettings extends SettingsPreferenceFragment {
super.onResume();
mAirplaneModeEnabler.resume();
- mWifiEnabler.resume();
- mBtEnabler.resume();
mNfcEnabler.resume();
}
@@ -201,8 +175,6 @@ public class WirelessSettings extends SettingsPreferenceFragment {
super.onPause();
mAirplaneModeEnabler.pause();
- mWifiEnabler.pause();
- mBtEnabler.pause();
mNfcEnabler.pause();
}
diff --git a/src/com/android/settings/accounts/AccountPreferenceBase.java b/src/com/android/settings/accounts/AccountPreferenceBase.java
index a84bece..a0d6a7f 100644
--- a/src/com/android/settings/accounts/AccountPreferenceBase.java
+++ b/src/com/android/settings/accounts/AccountPreferenceBase.java
@@ -32,6 +32,7 @@ import android.content.Context;
import android.content.SyncAdapterType;
import android.content.SyncStatusObserver;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
@@ -113,7 +114,7 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
mAccountTypeToAuthorities.put(sa.accountType, authorities);
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "added authority " + sa.authority + " to accountType "
+ Log.d(TAG, "added authority " + sa.authority + " to accountType "
+ sa.accountType);
}
authorities.add(sa.authority);
@@ -136,7 +137,10 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
icon = authContext.getResources().getDrawable(desc.iconId);
} catch (PackageManager.NameNotFoundException e) {
// TODO: place holder icon for missing account icons?
- Log.w(TAG, "No icon for account type " + accountType);
+ Log.w(TAG, "No icon name for account type " + accountType);
+ } catch (Resources.NotFoundException e) {
+ // TODO: place holder icon for missing account icons?
+ Log.w(TAG, "No icon resource for account type " + accountType);
}
}
return icon;
@@ -155,7 +159,9 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
Context authContext = getActivity().createPackageContext(desc.packageName, 0);
label = authContext.getResources().getText(desc.labelId);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "No label for account type " + ", type " + accountType);
+ Log.w(TAG, "No label name for account type " + accountType);
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "No label icon for account type " + accountType);
}
}
return label;
@@ -179,6 +185,8 @@ class AccountPreferenceBase extends SettingsPreferenceFragment
}
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName);
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "Couldn't load preferences.xml file from " + desc.packageName);
}
}
return prefs;
diff --git a/src/com/android/settings/accounts/AccountSyncSettings.java b/src/com/android/settings/accounts/AccountSyncSettings.java
index 9ef0481..547b0e1 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);
@@ -486,11 +492,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/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..7d935f0 100644
--- a/src/com/android/settings/accounts/ManageAccountsSettings.java
+++ b/src/com/android/settings/accounts/ManageAccountsSettings.java
@@ -19,7 +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;
diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java
index 11e4aae..519c203 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 {
@@ -97,6 +95,8 @@ public class ApplicationsState {
ApplicationInfo info;
Drawable icon;
String sizeStr;
+ String internalSizeStr;
+ String externalSizeStr;
boolean sizeStale;
long sizeLoadStart;
@@ -392,6 +392,14 @@ public class ApplicationsState {
for (int i=0; i<mApplications.size(); i++) {
final ApplicationInfo info = mApplications.get(i);
+ // Need to trim out any applications that are disabled by
+ // something different than the user.
+ if (!info.enabled && info.enabledSetting
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ mApplications.remove(i);
+ i--;
+ continue;
+ }
final AppEntry entry = mEntriesMap.get(info.packageName);
if (entry != null) {
entry.info = info;
@@ -652,8 +660,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 +693,27 @@ 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.internalSizeStr = getSizeStr(getTotalInternalSize(stats));
+ entry.externalSizeStr = getSizeStr(getTotalExternalSize(stats));
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..ab46661 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;
@@ -282,7 +284,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 +333,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 +550,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;
@@ -831,7 +838,7 @@ public class InstalledAppDetails extends Fragment
} else {
if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
new DisableChanger(this, mAppEntry.info, mAppEntry.info.enabled ?
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
: PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).execute((Object)null);
} else {
uninstallPkg(packageName);
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 85db45e..554ece3 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;
@@ -399,7 +423,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 +502,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 +801,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 +819,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/bluetooth/AdvancedBluetoothSettings.java b/src/com/android/settings/bluetooth/AdvancedBluetoothSettings.java
new file mode 100644
index 0000000..83371cd
--- /dev/null
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothSettings.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+public class AdvancedBluetoothSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_BT_DISCOVERABLE = "bt_discoverable";
+ private static final String KEY_BT_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout";
+ private static final String KEY_BT_NAME = "bt_name";
+ private static final String KEY_BT_SHOW_RECEIVED = "bt_show_received_files";
+
+ /* Private intent to show the list of received files */
+ private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
+ "android.btopp.intent.action.OPEN_RECEIVED_FILES";
+
+ private BluetoothDiscoverableEnabler mDiscoverableEnabler;
+ private BluetoothNamePreference mNamePreference;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.bluetooth_advanced_settings);
+
+ LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(getActivity());
+ if (localManager != null) {
+ LocalBluetoothAdapter localAdapter = localManager.getBluetoothAdapter();
+ mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(),
+ localAdapter,
+ (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE),
+ (ListPreference) findPreference(KEY_BT_DISCOVERABLE_TIMEOUT));
+ }
+
+ mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mDiscoverableEnabler.resume();
+ mNamePreference.resume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ mNamePreference.pause();
+ mDiscoverableEnabler.pause();
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ if (KEY_BT_SHOW_RECEIVED.equals(preference.getKey())) {
+ Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
+ getActivity().sendBroadcast(intent);
+ return true;
+ }
+
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ return true;
+ }
+}
diff --git a/src/com/android/settings/bluetooth/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..06c708b 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -49,8 +49,6 @@ public final class BluetoothDevicePreference extends Preference implements
private final CachedBluetoothDevice mCachedDevice;
- private ImageView mDeviceSettings;
-
private OnClickListener mOnSettingsClickListener;
private AlertDialog mDisconnectDialog;
@@ -121,13 +119,13 @@ public final class BluetoothDevicePreference extends Preference implements
btClass.setImageResource(getBtClassDrawable());
btClass.setAlpha(isEnabled() ? 255 : sDimAlpha);
btClass.setVisibility(View.VISIBLE);
- mDeviceSettings = (ImageView) view.findViewById(R.id.deviceDetails);
+ ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails);
if (mOnSettingsClickListener != null) {
- mDeviceSettings.setOnClickListener(this);
- mDeviceSettings.setTag(mCachedDevice);
- mDeviceSettings.setAlpha(isEnabled() ? 255 : sDimAlpha);
+ deviceDetails.setOnClickListener(this);
+ deviceDetails.setTag(mCachedDevice);
+ deviceDetails.setAlpha(isEnabled() ? 255 : sDimAlpha);
} else { // Hide the settings icon and divider
- mDeviceSettings.setVisibility(View.GONE);
+ deviceDetails.setVisibility(View.GONE);
View divider = view.findViewById(R.id.divider);
if (divider != null) {
divider.setVisibility(View.GONE);
@@ -152,13 +150,13 @@ public final class BluetoothDevicePreference extends Preference implements
}
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 +165,7 @@ public final class BluetoothDevicePreference extends Preference implements
((BluetoothDevicePreference) o).mCachedDevice);
}
+ @Override
public int hashCode() {
return mCachedDevice.hashCode();
}
@@ -174,8 +173,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
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/BluetoothPermissionActivity.java b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
new file mode 100644
index 0000000..1a0965c
--- /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.getName() : 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.getName() : 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..c769ba6
--- /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.getName() : 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/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index 5e4e130..2208223 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -16,16 +16,28 @@
package com.android.settings.bluetooth;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
-import android.preference.CheckBoxPreference;
-import android.preference.ListPreference;
+import android.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 +47,253 @@ import com.android.settings.R;
public final class BluetoothSettings extends DeviceListPreferenceFragment {
private static final String TAG = "BluetoothSettings";
- private static final String KEY_BT_CHECKBOX = "bt_checkbox";
- private static final String KEY_BT_DISCOVERABLE = "bt_discoverable";
- private static final String KEY_BT_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout";
- private static final String KEY_BT_NAME = "bt_name";
- private static final String KEY_BT_SHOW_RECEIVED = "bt_show_received_files";
+ private static final int MENU_ID_SCAN = Menu.FIRST;
+ private static final int MENU_ID_ADVANCED = Menu.FIRST + 1;
- private BluetoothEnabler mEnabler;
- private BluetoothDiscoverableEnabler mDiscoverableEnabler;
- private BluetoothNamePreference mNamePreference;
+ private BluetoothEnabler mBluetoothEnabler;
- /* Private intent to show the list of received files */
- private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
- "android.btopp.intent.action.OPEN_RECEIVED_FILES";
+ private PreferenceGroup mAvailableDevicesCategory;
+ private boolean mAvailableDevicesCategoryIsPresent;
- /** Initialize the filter to show bonded devices only. */
- public BluetoothSettings() {
- super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
+ private View mView;
+ private TextView mEmptyView;
+
+ @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));
+ }
+ }
- mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
+ mBluetoothEnabler = new BluetoothEnabler(activity, actionBarSwitch);
+
+ setHasOptionsMenu(true);
}
@Override
public void onResume() {
super.onResume();
- // Repopulate (which isn't too bad since it's cached in the settings
- // bluetooth manager)
- addDevices();
+ mBluetoothEnabler.resume();
- mEnabler.resume();
- mDiscoverableEnabler.resume();
- mNamePreference.resume();
+ updateContent(mLocalAdapter.getBluetoothState());
}
@Override
public void onPause() {
super.onPause();
- mNamePreference.pause();
- mDiscoverableEnabler.pause();
- mEnabler.pause();
+ mBluetoothEnabler.pause();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON;
+ 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)
+ //.setIcon(R.drawable.ic_menu_scan_network)
+ .setEnabled(bluetoothIsEnabled && !isDiscovering)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.bluetooth_menu_advanced)
+ //.setIcon(android.R.drawable.ic_menu_manage)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ }
+
+ @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_ADVANCED:
+ if (getActivity() instanceof PreferenceActivity) {
+ ((PreferenceActivity) getActivity()).startPreferencePanel(
+ AdvancedBluetoothSettings.class.getCanonicalName(),
+ null,
+ R.string.bluetooth_advanced_titlebar, null,
+ this, 0);
+ } else {
+ startFragment(this, AdvancedBluetoothSettings.class.getCanonicalName(), -1, null);
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void startScanning() {
+ if (!mAvailableDevicesCategoryIsPresent) {
+ getPreferenceScreen().addPreference(mAvailableDevicesCategory);
+ }
+ mLocalAdapter.startScanning(true);
+ }
+
+ @Override
+ 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();
+ getActivity().invalidateOptionsMenu();
+ int messageId = 0;
+
+ switch (bluetoothState) {
+ case BluetoothAdapter.STATE_ON:
+ preferenceScreen.removeAll();
+ preferenceScreen.setOrderingAsAdded(true);
+
+ // This device
+ if (mMyDevicePreference == null) {
+ mMyDevicePreference = new Preference(getActivity());
+ }
+ if (mLocalAdapter != null) {
+ mMyDevicePreference.setTitle(mLocalAdapter.getName());
+ }
+ mMyDevicePreference.setEnabled(true);
+ preferenceScreen.addPreference(mMyDevicePreference);
+
+ // 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();
+
+ // 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();
+ }
+ return; // not break
+
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ int preferenceCount = preferenceScreen.getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ preferenceScreen.getPreference(i).setEnabled(false);
+ }
+ return; // not 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;
+ }
+
+ setDeviceListGroup(preferenceScreen);
+ removeAllDevices();
+ mEmptyView.setText(messageId);
+ }
+
+ @Override
+ public void onBluetoothStateChanged(int bluetoothState) {
+ super.onBluetoothStateChanged(bluetoothState);
+ updateContent(bluetoothState);
}
- private final View.OnClickListener mListener = new View.OnClickListener() {
+ @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());
+ }
+
+ 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();
+ CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
Preference pref = new Preference(getActivity());
pref.setTitle(device.getName());
pref.setFragment(DeviceProfilesSettings.class.getName());
pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE,
device.getDevice());
- ((PreferenceActivity) getActivity())
- .onPreferenceStartFragment(BluetoothSettings.this,
- pref);
+ ((PreferenceActivity) getActivity()).onPreferenceStartFragment(
+ BluetoothSettings.this, pref);
} else {
- Log.w(TAG, "onClick() called for other View: " + v);
+ Log.w(TAG, "onClick() called for other View: " + v); // TODO remove
}
}
};
- @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;
- }
+ private Preference mMyDevicePreference;
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
-
- 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);
- }
- } else if (bondState == BluetoothDevice.BOND_NONE) {
- // remove unpaired device from paired devices list
- onDeviceDeleted(cachedDevice);
- }
- }
+ private PreferenceGroup mPairedDevicesCategory;
/**
* Add a listener, which enables the advanced settings icon.
@@ -143,6 +301,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/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
index a978e23..9783fd7 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) {
@@ -132,7 +135,7 @@ public abstract class DeviceListPreferenceFragment extends
}
if (preference instanceof BluetoothDevicePreference) {
- BluetoothDevicePreference btPreference = (BluetoothDevicePreference)preference;
+ BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
CachedBluetoothDevice device = btPreference.getCachedDevice();
mSelectedDevice = device.getDevice();
onDevicePreferenceClick(btPreference);
@@ -152,6 +155,9 @@ public abstract class DeviceListPreferenceFragment extends
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 +168,7 @@ public abstract class DeviceListPreferenceFragment extends
getActivity(), cachedDevice);
initDevicePreference(preference);
- mDeviceList.addPreference(preference);
+ mDeviceListGroup.addPreference(preference);
mDevicePreferenceMap.put(cachedDevice, preference);
}
@@ -170,13 +176,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 +192,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..ecb7112 100644
--- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
+++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
@@ -50,7 +50,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
private static final String KEY_RENAME_DEVICE = "rename_device";
private static final String KEY_PROFILE_CONTAINER = "profile_container";
private static final String KEY_UNPAIR = "unpair";
- private static final String KEY_ALLOW_INCOMING = "allow_incoming";
+ //private static final String KEY_ALLOW_INCOMING = "allow_incoming";
public static final String EXTRA_DEVICE = "device";
@@ -355,6 +355,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
mCachedDevice.unpair();
}
+ /*
private void setIncomingFileTransfersAllowed(boolean allow) {
// TODO: make an IPC call into BluetoothOpp to update
Log.d(TAG, "Set allow incoming = " + allow);
@@ -364,6 +365,7 @@ public final class DeviceProfilesSettings extends SettingsPreferenceFragment
// TODO: get this value from BluetoothOpp ???
return true;
}
+ */
private boolean getAutoConnect(LocalBluetoothProfile prof) {
return prof.isPreferred(mCachedDevice.getDevice());
diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java
index db60c37..fff1570 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_IF_ROOM);
+ }
+
+ @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/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..ae6d817 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)) {
@@ -255,7 +258,8 @@ public class StorageVolumePreferenceCategory extends PreferenceCategory implemen
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 +311,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..5da13af
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/UsbSettings.java
@@ -0,0 +1,150 @@
+/*
+ * 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.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.io.File;
+
+/**
+ * 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 static final String KEY_INSTALLER_CD = "usb_installer_cd";
+ private static final int MENU_ID_INSTALLER_CD = Menu.FIRST;
+
+ private UsbManager mUsbManager;
+ private String mInstallerImagePath;
+ private CheckBoxPreference mMtp;
+ private CheckBoxPreference mPtp;
+
+ 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);
+ mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath);
+ if (!(new File(mInstallerImagePath)).exists()) {
+ mInstallerImagePath = null;
+ }
+ setHasOptionsMenu(mInstallerImagePath != null);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Make sure we reload the preference hierarchy since some of these settings
+ // depend on others...
+ createPreferenceHierarchy();
+ updateToggles();
+ }
+
+ private void updateToggles() {
+ if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) {
+ mMtp.setChecked(true);
+ mPtp.setChecked(false);
+ } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) {
+ mMtp.setChecked(false);
+ mPtp.setChecked(true);
+ } else {
+ mMtp.setChecked(false);
+ mPtp.setChecked(false);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ Log.d(TAG, "onPreferenceTreeClick " + 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);
+ mPtp.setChecked(false);
+ } else if (preference == mPtp) {
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true);
+ mMtp.setChecked(false);
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(Menu.NONE, MENU_ID_INSTALLER_CD, 0, R.string.usb_label_installer_cd)
+ //.setIcon(com.android.internal.R.drawable.stat_sys_data_usb)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ID_INSTALLER_CD:
+ // installer CD is never default
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MASS_STORAGE, false);
+ mUsbManager.setMassStorageBackingFile(mInstallerImagePath);
+ mMtp.setChecked(false);
+ mPtp.setChecked(false);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
index 97ebf43..13a962d 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
@@ -368,9 +368,9 @@ public class BatteryHistoryChart extends View {
}
if (rec.batteryLevel != lastLevel || pos == 1) {
lastLevel = rec.batteryLevel;
- lastInteresting = pos;
- mHistEnd = rec.time;
}
+ lastInteresting = pos;
+ mHistEnd = rec.time;
aggrStates |= rec.states;
}
}
@@ -438,7 +438,13 @@ public class BatteryHistoryChart extends View {
2, getResources().getDisplayMetrics());
if (h > (textHeight*6)) {
mLargeMode = true;
- mLineWidth = textHeight/2;
+ if (h > (textHeight*15)) {
+ // Plenty of room for the chart.
+ mLineWidth = textHeight/2;
+ } else {
+ // Compress lines to make more room for chart.
+ mLineWidth = textHeight/3;
+ }
mLevelTop = textHeight + mLineWidth;
mScreenOnPaint.setARGB(255, 32, 64, 255);
mGpsOnPaint.setARGB(255, 32, 64, 255);
@@ -472,7 +478,8 @@ public class BatteryHistoryChart extends View {
mWifiRunningOffset = mWakeLockOffset + barOffset;
mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? barOffset : 0);
mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? barOffset : 0);
- mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0) + mLineWidth;
+ mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0)
+ + ((mLineWidth*3)/2);
if (mHavePhoneSignal) {
mPhoneSignalChart.init(w);
}
@@ -670,8 +677,8 @@ public class BatteryHistoryChart extends View {
if (!mBatCriticalPath.isEmpty()) {
canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
}
- int top = height - (mHavePhoneSignal ? mPhoneSignalOffset - (mLineWidth/2) : 0);
if (mHavePhoneSignal) {
+ int top = height-mPhoneSignalOffset - (mLineWidth/2);
mPhoneSignalChart.draw(canvas, top, mLineWidth);
}
if (!mScreenOnPath.isEmpty()) {
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index a4808b0..4ccebf0 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -18,28 +18,61 @@ package com.android.settings.inputmethod;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.UserDictionarySettings;
import com.android.settings.Utils;
import com.android.settings.VoiceInputOutputSettings;
+import android.app.Activity;
import android.content.Context;
+import android.content.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 +92,26 @@ 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"));
}
private void updateInputMethodSelectorSummary(int value) {
@@ -77,25 +123,71 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
}
}
+ private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
+ final Activity activity = getActivity();
+ final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity);
+ if (localeList.size() <= 1) {
+ userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title);
+ userDictionaryPreference.setFragment(UserDictionarySettings.class.getName());
+ // If the size of localeList is 0, we don't set the locale parameter in the
+ // extras. This will be interpreted by the UserDictionarySettings class as
+ // meaning "the current locale".
+ // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesList()
+ // the locale list always has at least one element, since it always includes the current
+ // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesList().
+ if (localeList.size() == 1) {
+ final String locale = (String)localeList.toArray()[0];
+ userDictionaryPreference.getExtras().putString("locale", locale);
+ }
+ } else {
+ userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title);
+ userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
+ }
+ }
+
@Override
public void onResume() {
super.onResume();
- 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 +204,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 +237,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..43d54a2 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeEnabler.java
@@ -188,7 +188,8 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
private PreferenceScreen createPreferenceHierarchy() {
// Root
- PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
+ final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
+ final Context context = getActivity();
int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size());
@@ -202,7 +203,7 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
if (!TextUtils.isEmpty(mInputMethodId) && !mInputMethodId.equals(imiId)) {
continue;
}
- PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(getActivity());
+ PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context);
root.addPreference(keyboardSettingsCategory);
PackageManager pm = getPackageManager();
CharSequence label = imi.loadLabel(pm);
@@ -210,31 +211,22 @@ public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
keyboardSettingsCategory.setTitle(label);
keyboardSettingsCategory.setKey(imiId);
// TODO: Use toggle Preference if images are ready.
- CheckBoxPreference autoCB = new CheckBoxPreference(getActivity());
+ CheckBoxPreference autoCB = new CheckBoxPreference(context);
autoCB.setTitle(R.string.use_system_language_to_select_input_method_subtypes);
mSubtypeAutoSelectionCBMap.put(imiId, autoCB);
keyboardSettingsCategory.addPreference(autoCB);
- PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(getActivity());
+ PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context);
activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
root.addPreference(activeInputMethodsCategory);
ArrayList<Preference> subtypePreferences = new ArrayList<Preference>();
if (subtypeCount > 0) {
for (int j = 0; j < subtypeCount; ++j) {
- InputMethodSubtype subtype = imi.getSubtypeAt(j);
- CharSequence subtypeLabel;
- int nameResId = subtype.getNameResId();
- if (nameResId != 0) {
- subtypeLabel = pm.getText(imi.getPackageName(), nameResId,
- imi.getServiceInfo().applicationInfo);
- } else {
- String mode = subtype.getMode();
- CharSequence language = subtype.getLocale();
- subtypeLabel = (mode == null ? "" : mode) + ","
- + (language == null ? "" : language);
- }
- CheckBoxPreference chkbxPref = new CheckBoxPreference(getActivity());
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ final CharSequence subtypeLabel = subtype.getDisplayName(context,
+ imi.getPackageName(), imi.getServiceInfo().applicationInfo);
+ final CheckBoxPreference chkbxPref = new CheckBoxPreference(context);
chkbxPref.setKey(imiId + subtype.hashCode());
chkbxPref.setTitle(subtypeLabel);
activeInputMethodsCategory.addPreference(chkbxPref);
diff --git a/src/com/android/settings/inputmethod/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..21057a6
--- /dev/null
+++ b/src/com/android/settings/inputmethod/InputMethodPreference.java
@@ -0,0 +1,242 @@
+/*
+ * 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.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) {
+ mFragment.startActivity(mSettingsIntent);
+ }
+ });
+ }
+ 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/UserDictionaryList.java b/src/com/android/settings/inputmethod/UserDictionaryList.java
new file mode 100644
index 0000000..5db2841
--- /dev/null
+++ b/src/com/android/settings/inputmethod/UserDictionaryList.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.UserDictionarySettings;
+import com.android.settings.Utils;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+import android.provider.UserDictionary;
+
+import java.util.Locale;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class UserDictionaryList extends SettingsPreferenceFragment {
+
+ private static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION =
+ "android.settings.USER_DICTIONARY_SETTINGS";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
+ }
+
+ static Set<String> getUserDictionaryLocalesList(Activity activity) {
+ final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
+ new String[] { UserDictionary.Words.LOCALE },
+ null, null, null);
+ final Set<String> localeList = new TreeSet<String>();
+ if (cursor.moveToFirst()) {
+ final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
+ do {
+ String locale = cursor.getString(columnIndex);
+ localeList.add(null != locale ? locale : "");
+ } while (cursor.moveToNext());
+ }
+ localeList.add(Locale.getDefault().toString());
+ return localeList;
+ }
+
+ /**
+ * Creates the entries that allow the user to go into the user dictionary for each locale.
+ * @param userDictGroup The group to put the settings in.
+ */
+ protected void createUserDictSettings(PreferenceGroup userDictGroup) {
+ final Activity activity = getActivity();
+ userDictGroup.removeAll();
+ final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity);
+
+ if (localeList.isEmpty()) {
+ userDictGroup.addPreference(createUserDictionaryPreference(null, activity));
+ } else {
+ for (String locale : localeList) {
+ userDictGroup.addPreference(createUserDictionaryPreference(locale, activity));
+ }
+ }
+ }
+
+ /**
+ * Create a single User Dictionary Preference object, with its parameters set.
+ * @param locale The locale for which this user dictionary is for.
+ * @return The corresponding preference.
+ */
+ protected Preference createUserDictionaryPreference(String locale, Activity activity) {
+ final Preference newPref = new Preference(getActivity());
+ final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION);
+ if (null == locale) {
+ newPref.setTitle(Locale.getDefault().getDisplayName());
+ } else {
+ if ("".equals(locale))
+ newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
+ else
+ newPref.setTitle(Utils.createLocaleFromString(locale).getDisplayName());
+ intent.putExtra("locale", locale);
+ newPref.getExtras().putString("locale", locale);
+ }
+ newPref.setIntent(intent);
+ newPref.setFragment(UserDictionarySettings.class.getName());
+ return newPref;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ createUserDictSettings(getPreferenceScreen());
+ }
+}
diff --git a/src/com/android/settings/net/NetworkPolicyEditor.java b/src/com/android/settings/net/NetworkPolicyEditor.java
new file mode 100644
index 0000000..61c2550
--- /dev/null
+++ b/src/com/android/settings/net/NetworkPolicyEditor.java
@@ -0,0 +1,172 @@
+/*
+ * 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.WARNING_DISABLED;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+
+import com.android.internal.util.Objects;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to modify list of {@link NetworkPolicy}. Specifically knows
+ * about which policies can coexist.
+ */
+public class NetworkPolicyEditor {
+ // TODO: be more robust when missing policies from service
+
+ private INetworkPolicyManager mPolicyService;
+ private ArrayList<NetworkPolicy> mPolicies = Lists.newArrayList();
+
+ public NetworkPolicyEditor(INetworkPolicyManager policyService) {
+ mPolicyService = checkNotNull(policyService);
+ }
+
+ public void read() {
+ try {
+ final NetworkPolicy[] policies = mPolicyService.getNetworkPolicies();
+ mPolicies.clear();
+ for (NetworkPolicy policy : policies) {
+ // 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) {
+ getPolicy(template).cycleDay = cycleDay;
+ writeAsync();
+ }
+
+ public void setPolicyWarningBytes(NetworkTemplate template, long warningBytes) {
+ getPolicy(template).warningBytes = warningBytes;
+ writeAsync();
+ }
+
+ public void setPolicyLimitBytes(NetworkTemplate template, long limitBytes) {
+ getPolicy(template).limitBytes = limitBytes;
+ writeAsync();
+ }
+
+ public boolean isMobilePolicySplit(String subscriberId) {
+ boolean has3g = false;
+ boolean has4g = false;
+ for (NetworkPolicy policy : mPolicies) {
+ final NetworkTemplate template = policy.template;
+ if (Objects.equal(subscriberId, template.getSubscriberId())) {
+ switch (template.getMatchRule()) {
+ case MATCH_MOBILE_3G_LOWER:
+ has3g = true;
+ break;
+ case MATCH_MOBILE_4G:
+ has4g = true;
+ break;
+ }
+ }
+ }
+ return has3g && has4g;
+ }
+
+ public void setMobilePolicySplit(String subscriberId, boolean split) {
+ final boolean beforeSplit = isMobilePolicySplit(subscriberId);
+
+ final NetworkTemplate template3g = new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId);
+ final NetworkTemplate template4g = new NetworkTemplate(MATCH_MOBILE_4G, subscriberId);
+ final NetworkTemplate templateAll = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId);
+
+ if (split == beforeSplit) {
+ // already in requested state; skip
+ return;
+
+ } else if (beforeSplit && !split) {
+ // combine, picking most restrictive policy
+ final NetworkPolicy policy3g = getPolicy(template3g);
+ final NetworkPolicy policy4g = getPolicy(template4g);
+
+ final NetworkPolicy restrictive = policy3g.compareTo(policy4g) < 0 ? policy3g
+ : policy4g;
+ mPolicies.remove(policy3g);
+ mPolicies.remove(policy4g);
+ mPolicies.add(
+ new NetworkPolicy(templateAll, restrictive.cycleDay, restrictive.warningBytes,
+ restrictive.limitBytes));
+ writeAsync();
+
+ } else if (!beforeSplit && split) {
+ // duplicate existing policy into two rules
+ final NetworkPolicy policyAll = getPolicy(templateAll);
+ mPolicies.remove(policyAll);
+ mPolicies.add(new NetworkPolicy(
+ template3g, policyAll.cycleDay, policyAll.warningBytes, policyAll.limitBytes));
+ mPolicies.add(new NetworkPolicy(
+ template4g, policyAll.cycleDay, policyAll.warningBytes, policyAll.limitBytes));
+ writeAsync();
+
+ }
+ }
+
+}
diff --git a/src/com/android/settings/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/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..4f9d0a2
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnDialog.java
@@ -0,0 +1,327 @@
+/*
+ * 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.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+class VpnDialog extends AlertDialog implements TextWatcher, OnItemSelectedListener {
+ private static final String DUMMY = "\r";
+
+ private static String getDummy(String secret) {
+ return secret.isEmpty() ? "" : DUMMY;
+ }
+
+ private static String getSecret(String oldSecret, TextView view) {
+ String newSecret = view.getText().toString();
+ return DUMMY.equals(newSecret) ? oldSecret : newSecret;
+ }
+
+ 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 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);
+ 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(getDummy(mProfile.password));
+ mSearchDomains.setText(mProfile.searchDomains);
+ mRoutes.setText(mProfile.routes);
+ mMppe.setChecked(mProfile.mppe);
+ mL2tpSecret.setText(getDummy(mProfile.l2tpSecret));
+ mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
+ mIpsecSecret.setText(getDummy(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);
+ 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);
+
+ // 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));
+ }
+
+ @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 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_id).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);
+ mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ mView.findViewById(R.id.ipsec_id).setVisibility(View.VISIBLE);
+ 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) {
+ return false;
+ }
+ switch (mType.getSelectedItemPosition()) {
+ case VpnProfile.TYPE_PPTP:
+ 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 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 = getSecret(mProfile.password, mPassword);
+ profile.searchDomains = mSearchDomains.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 = getSecret(mProfile.l2tpSecret, mL2tpSecret);
+ profile.ipsecSecret = getSecret(mProfile.ipsecSecret, mIpsecSecret);
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
+ profile.ipsecSecret = getSecret(mProfile.ipsecSecret, mIpsecSecret);
+ break;
+
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ profile.l2tpSecret = getSecret(mProfile.l2tpSecret, mL2tpSecret);
+ // 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..24c2f5f
--- /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 = false; // 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..f345c22
--- /dev/null
+++ b/src/com/android/settings/vpn2/VpnSettings.java
@@ -0,0 +1,473 @@
+/*
+ * 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.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.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);
+ PreferenceGroup group = getPreferenceScreen();
+ group.setOrderingAsAdded(false);
+ group.findPreference("add_network").setOnPreferenceClickListener(this);
+
+ 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!
+ getActivity().getFragmentManager().popBackStack();
+ }
+ 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>();
+
+ 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);
+ }
+ }
+ }
+ }
+ PreferenceGroup group = getPreferenceScreen();
+ for (VpnPreference preference : mPreferences.values()) {
+ group.addPreference(preference);
+ }
+
+ // 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.
+ 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 void connect(VpnProfile profile) throws Exception {
+ // Get the current active interface.
+ LinkProperties network = mService.getActiveLinkProperties();
+ String interfaze = (network == null) ? null : network.getInterfaceName();
+ if (interfaze == null) {
+ throw new IllegalStateException("Cannot get network interface");
+ }
+
+ // 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");
+ }
+ Log.i(TAG, userCert);
+
+ // Prepare arguments for racoon.
+ String[] racoon = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ racoon = new String[] {
+ interfaze, profile.server, "udppsk", "1701", profile.ipsecSecret,
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ racoon = new String[] {
+ interfaze, profile.server, "udprsa", "1701", privateKey, userCert, caCert,
+ };
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ break;
+ case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ break;
+ case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ break;
+ }
+
+ // Prepare arguments for mtpd.
+ String[] mtpd = null;
+ switch (profile.type) {
+ case VpnProfile.TYPE_PPTP:
+ mtpd = new String[] {
+ "pptp", profile.server, "1723",
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ "ipparam", profile.routes, (profile.mppe ? "+mppe" : "nomppe"),
+ };
+ break;
+ case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+ case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+ mtpd = new String[] {
+ "l2tp", profile.server, "1701", profile.l2tpSecret,
+ "name", profile.username, "password", profile.password,
+ "linkname", "vpn", "refuse-eap", "nodefaultroute",
+ "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ "ipparam", profile.routes,
+ };
+ break;
+ }
+
+ VpnConfig config = new VpnConfig();
+ config.packagz = profile.key;
+ config.session = profile.name;
+ config.routes = profile.routes;
+ 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..463541f
--- /dev/null
+++ b/src/com/android/settings/widget/ChartAxis.java
@@ -0,0 +1,38 @@
+/*
+ * 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 {
+
+ public void setBounds(long min, long max);
+ public void setSize(float size);
+
+ public float convertToPoint(long value);
+ public long convertToValue(float point);
+
+ public void buildLabel(Resources res, SpannableStringBuilder builder, long value);
+
+ public float[] getTickPoints();
+
+}
diff --git a/src/com/android/settings/widget/ChartGridView.java b/src/com/android/settings/widget/ChartGridView.java
new file mode 100644
index 0000000..7a83fbf
--- /dev/null
+++ b/src/com/android/settings/widget/ChartGridView.java
@@ -0,0 +1,102 @@
+/*
+ * 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.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+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 {
+
+ // TODO: eventually teach about drawing chart labels
+
+ private ChartAxis mHoriz;
+ private ChartAxis mVert;
+
+ private Drawable mPrimary;
+ private Drawable mSecondary;
+ private Drawable mBorder;
+
+ 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);
+ // TODO: eventually read labelColor
+
+ a.recycle();
+ }
+
+ void init(ChartAxis horiz, ChartAxis vert) {
+ mHoriz = Preconditions.checkNotNull(horiz, "missing horiz");
+ mVert = Preconditions.checkNotNull(vert, "missing vert");
+ }
+
+ @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);
+ }
+}
diff --git a/src/com/android/settings/widget/ChartNetworkSeriesView.java b/src/com/android/settings/widget/ChartNetworkSeriesView.java
new file mode 100644
index 0000000..10d8976
--- /dev/null
+++ b/src/com/android/settings/widget/ChartNetworkSeriesView.java
@@ -0,0 +1,222 @@
+/*
+ * 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.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 = true;
+
+ private ChartAxis mHoriz;
+ private ChartAxis mVert;
+
+ private Paint mPaintStroke;
+ private Paint mPaintFill;
+ private Paint mPaintFillSecondary;
+
+ private NetworkStatsHistory mStats;
+
+ private Path mPathStroke;
+ private Path mPathFill;
+
+ private long mPrimaryLeft;
+ private long mPrimaryRight;
+
+ 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();
+ }
+
+ 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);
+ }
+
+ public void bindNetworkStats(NetworkStatsHistory stats) {
+ mStats = stats;
+
+ mPathStroke.reset();
+ mPathFill.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()");
+
+ mPathStroke.reset();
+ mPathFill.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;
+
+ // 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);
+
+ final float x = mHoriz.convertToPoint(entry.bucketStart);
+ 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;
+ }
+
+ if (LOGD) {
+ final RectF bounds = new RectF();
+ mPathFill.computeBounds(bounds, true);
+ Log.d(TAG, "onLayout() rendered with bounds=" + bounds.toString() + " and totalData="
+ + totalData);
+ }
+
+ // drop to bottom of graph from current location
+ mPathFill.lineTo(lastX, height);
+ mPathFill.lineTo(firstX, height);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int save;
+
+ final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft);
+ final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight);
+
+ 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..4e37657
--- /dev/null
+++ b/src/com/android/settings/widget/ChartSweepView.java
@@ -0,0 +1,437 @@
+/*
+ * 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 int mFollowAxis;
+
+ private int mLabelSize;
+ private int mLabelTemplateRes;
+ private int mLabelColor;
+
+ private SpannableStringBuilder mLabelTemplate;
+ private DynamicLayout mLabelLayout;
+
+ private ChartAxis mAxis;
+ private long mValue;
+
+ private ChartSweepView mClampAfter;
+ private ChartSweepView mClampBefore;
+
+ 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));
+
+ 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;
+ }
+ }
+
+ public void setClampAfter(ChartSweepView clampAfter) {
+ mClampAfter = clampAfter;
+ }
+
+ public void setClampBefore(ChartSweepView clampBefore) {
+ mClampBefore = clampBefore;
+ }
+
+ @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();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ case MotionEvent.ACTION_MOVE: {
+ getParent().requestDisallowInterceptTouchEvent(true);
+
+ // content area of parent
+ final Rect parentContent = new Rect(parent.getPaddingLeft(), parent.getPaddingTop(),
+ parent.getWidth() - parent.getPaddingRight(),
+ parent.getHeight() - parent.getPaddingBottom());
+ 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;
+ }
+ }
+ }
+
+ /**
+ * Compute {@link Rect} in {@link #getParent()} coordinates that we should
+ * be clamped inside of, usually from {@link #setClampAfter(ChartSweepView)}
+ * style rules.
+ */
+ private Rect computeClampRect(Rect parentContent) {
+ final Rect clampRect = new Rect(parentContent);
+
+ final ChartSweepView after = mClampAfter;
+ final ChartSweepView before = mClampBefore;
+
+ if (mFollowAxis == VERTICAL) {
+ if (after != null) {
+ clampRect.top += after.getPoint();
+ }
+ if (before != null) {
+ clampRect.bottom -= clampRect.height() - before.getPoint();
+ }
+ } else {
+ if (after != null) {
+ clampRect.left += after.getPoint();
+ }
+ if (before != null) {
+ clampRect.right -= clampRect.width() - before.getPoint();
+ }
+ }
+ 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..affede0
--- /dev/null
+++ b/src/com/android/settings/widget/DataUsageChartView.java
@@ -0,0 +1,406 @@
+/*
+ * 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.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;
+
+ // TODO: enforce that sweeps cant cross each other
+
+ private ChartGridView mGrid;
+ private ChartNetworkSeriesView mSeries;
+ private ChartNetworkSeriesView mDetailSeries;
+
+ private ChartSweepView mSweepLeft;
+ private ChartSweepView mSweepRight;
+ private ChartSweepView mSweepWarning;
+ private ChartSweepView mSweepLimit;
+
+ 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()));
+ }
+
+ @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.setClampBefore(mSweepRight);
+ mSweepRight.setClampAfter(mSweepLeft);
+ mSweepLimit.setClampBefore(mSweepWarning);
+ mSweepWarning.setClampAfter(mSweepLimit);
+
+ mSweepLeft.addOnSweepListener(mSweepListener);
+ mSweepRight.addOnSweepListener(mSweepListener);
+ mSweepWarning.addOnSweepListener(mWarningListener);
+ mSweepLimit.addOnSweepListener(mLimitListener);
+
+ // 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);
+ updatePrimaryRange();
+ requestLayout();
+ }
+
+ public void bindDetailNetworkStats(NetworkStatsHistory stats) {
+ mDetailSeries.bindNetworkStats(stats);
+ mDetailSeries.setVisibility(stats != null ? View.VISIBLE : View.GONE);
+ updatePrimaryRange();
+ requestLayout();
+ }
+
+ public void bindNetworkPolicy(NetworkPolicy policy) {
+ if (policy == null) {
+ mSweepLimit.setVisibility(View.INVISIBLE);
+ mSweepWarning.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
+ mSweepLimit.setVisibility(View.VISIBLE);
+ mSweepLimit.setValue(policy.limitBytes);
+ mSweepLimit.setEnabled(true);
+ } else {
+ mSweepLimit.setVisibility(View.VISIBLE);
+ mSweepLimit.setEnabled(false);
+ }
+
+ if (policy.warningBytes != NetworkPolicy.WARNING_DISABLED) {
+ mSweepWarning.setVisibility(View.VISIBLE);
+ mSweepWarning.setValue(policy.warningBytes);
+ } else {
+ mSweepWarning.setVisibility(View.INVISIBLE);
+ }
+
+ requestLayout();
+ }
+
+ private OnSweepListener mSweepListener = new OnSweepListener() {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone) {
+ updatePrimaryRange();
+
+ // update detail list only when done sweeping
+ if (sweepDone && mListener != null) {
+ mListener.onInspectRangeChanged();
+ }
+ }
+ };
+
+ private OnSweepListener mWarningListener = new OnSweepListener() {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone) {
+ if (sweepDone && mListener != null) {
+ mListener.onWarningChanged();
+ }
+ }
+ };
+
+ private OnSweepListener mLimitListener = new OnSweepListener() {
+ public void onSweep(ChartSweepView sweep, boolean sweepDone) {
+ if (sweepDone && mListener != null) {
+ mListener.onLimitChanged();
+ }
+ }
+ };
+
+ @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;
+ }
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!isActivated()) {
+ return true;
+ } else {
+ return super.onInterceptTouchEvent(ev);
+ }
+ }
+
+ public long getInspectStart() {
+ return mSweepLeft.getValue();
+ }
+
+ public long getInspectEnd() {
+ return mSweepRight.getValue();
+ }
+
+ public long getWarningBytes() {
+ return mSweepWarning.getValue();
+ }
+
+ public long getLimitBytes() {
+ return mSweepLimit.getValue();
+ }
+
+ /**
+ * Set the exact time range that should be displayed, updating how
+ * {@link ChartNetworkSeriesView} paints. Moves inspection ranges to be the
+ * last "week" of available data, without triggering listener events.
+ */
+ public void setVisibleRange(long start, long end, long dataBoundary) {
+ mHoriz.setBounds(start, end);
+
+ // default sweeps to last week of data
+ final long halfRange = (end + start) / 2;
+ final long sweepMax = Math.min(end, dataBoundary);
+ final long sweepMin = Math.max(start, (sweepMax - DateUtils.WEEK_IN_MILLIS));
+
+ mSweepLeft.setValue(sweepMin);
+ mSweepRight.setValue(sweepMax);
+ updatePrimaryRange();
+
+ requestLayout();
+ mSeries.generatePath();
+ mSeries.invalidate();
+ }
+
+ 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;
+ }
+ }
+
+ public static class DataAxis implements ChartAxis {
+ private long mMin;
+ private long mMax;
+ private float mSize;
+
+ public DataAxis() {
+ // TODO: adapt ranges to show when history >5GB, and handle 4G
+ // interfaces with higher limits.
+ setBounds(0, 5 * GB_IN_BYTES);
+ }
+
+ /** {@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) {
+ // TODO: this assumes range of [0,5]GB
+ final double fraction = Math.pow(
+ 10, 0.36884343106175160321 * Math.log10(value) + -3.62828151137812282556);
+ return (float) fraction * mSize;
+ }
+
+ /** {@inheritDoc} */
+ public long convertToValue(float point) {
+ final double y = point / mSize;
+ // TODO: this assumes range of [0,5]GB
+ final double fraction = 6.869341163271789302 * Math.pow(10, 9)
+ * Math.pow(y, 2.71117746931646030774);
+ return (long) fraction;
+ }
+
+ 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 float[] tickPoints = new float[16];
+
+ final long jump = ((mMax - mMin) / tickPoints.length);
+ long value = mMin;
+ for (int i = 0; i < tickPoints.length; i++) {
+ tickPoints[i] = convertToPoint(value);
+ value += jump;
+ }
+
+ return tickPoints;
+ }
+ }
+
+ 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..e589da9
--- /dev/null
+++ b/src/com/android/settings/widget/InvertedChartAxis.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.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;
+ }
+}
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..a8ce94f 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -16,33 +16,29 @@
package com.android.settings.wifi;
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+
import android.content.Context;
-import android.content.DialogInterface;
import android.content.res.Resources;
-import android.net.DhcpInfo;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkUtils;
-import android.net.Proxy;
import android.net.ProxyProperties;
import android.net.RouteInfo;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.AuthAlgorithm;
+import android.net.wifi.WifiConfiguration.IpAssignment;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WpsConfiguration;
-import android.net.wifi.WpsConfiguration.Setup;
-
-import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
import android.net.wifi.WifiConfiguration.ProxySettings;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WpsConfiguration;
+import android.net.wifi.WpsConfiguration.Setup;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
-import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -57,12 +53,10 @@ import com.android.settings.ProxySelector;
import com.android.settings.R;
import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
import java.util.Iterator;
/**
- * The class for allowing UIs like {@link WifiDialog} and {@link WifiConfigPreference} to
+ * The class for allowing UIs like {@link WifiDialog} and {@link WifiConfigUiBase} to
* share the logic for controlling buttons, text fields, etc.
*/
public class WifiConfigController implements TextWatcher,
@@ -429,7 +423,9 @@ public class WifiConfigController implements TextWatcher,
int networkPrefixLength = -1;
try {
networkPrefixLength = Integer.parseInt(mNetworkPrefixLengthView.getText().toString());
- } catch (NumberFormatException e) { }
+ } catch (NumberFormatException e) {
+ // Use -1
+ }
if (networkPrefixLength < 0 || networkPrefixLength > 32) {
return R.string.wifi_ip_settings_invalid_network_prefix_length;
}
@@ -698,6 +694,7 @@ public class WifiConfigController implements TextWatcher,
private void setSelection(Spinner spinner, String value) {
if (value != null) {
+ @SuppressWarnings("unchecked")
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinner.getAdapter();
for (int i = adapter.getCount() - 1; i >= 0; --i) {
if (value.equals(adapter.getItem(i))) {
@@ -719,10 +716,12 @@ public class WifiConfigController implements TextWatcher,
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // work done in afterTextChanged
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // work done in afterTextChanged
}
@Override
@@ -750,5 +749,6 @@ public class WifiConfigController implements TextWatcher,
@Override
public void onNothingSelected(AdapterView<?> parent) {
+ //
}
}
diff --git a/src/com/android/settings/wifi/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java
index 7f1221e..223022d 100644
--- a/src/com/android/settings/wifi/WifiEnabler.java
+++ b/src/com/android/settings/wifi/WifiEnabler.java
@@ -16,9 +16,6 @@
package com.android.settings.wifi;
-import com.android.settings.R;
-import com.android.settings.WirelessSettings;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -27,19 +24,19 @@ import android.net.NetworkInfo;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.preference.Preference;
-import android.preference.CheckBoxPreference;
import android.provider.Settings;
-import android.text.TextUtils;
+import android.widget.CompoundButton;
+import android.widget.Switch;
import android.widget.Toast;
+import com.android.settings.R;
+import com.android.settings.WirelessSettings;
+
import java.util.concurrent.atomic.AtomicBoolean;
-public class WifiEnabler implements Preference.OnPreferenceChangeListener {
+public class WifiEnabler implements CompoundButton.OnCheckedChangeListener {
private final Context mContext;
- private final CheckBoxPreference mCheckBox;
- private final CharSequence mOriginalSummary;
-
+ private Switch mSwitch;
private AtomicBoolean mConnected = new AtomicBoolean(false);
private final WifiManager mWifiManager;
@@ -65,11 +62,9 @@ public class WifiEnabler implements Preference.OnPreferenceChangeListener {
}
};
- public WifiEnabler(Context context, CheckBoxPreference checkBox) {
+ public WifiEnabler(Context context, Switch switch_) {
mContext = context;
- mCheckBox = checkBox;
- mOriginalSummary = checkBox.getSummary();
- checkBox.setPersistent(false);
+ mSwitch = switch_;
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
@@ -81,78 +76,86 @@ public class WifiEnabler implements Preference.OnPreferenceChangeListener {
public void resume() {
// Wi-Fi state is sticky, so just let the receiver update UI
mContext.registerReceiver(mReceiver, mIntentFilter);
- mCheckBox.setOnPreferenceChangeListener(this);
+ mSwitch.setOnCheckedChangeListener(this);
}
public void pause() {
mContext.unregisterReceiver(mReceiver);
- mCheckBox.setOnPreferenceChangeListener(null);
+ mSwitch.setOnCheckedChangeListener(null);
}
- public boolean onPreferenceChange(Preference preference, Object value) {
- boolean enable = (Boolean) value;
+ public void setSwitch(Switch switch_) {
+ if (mSwitch == switch_) return;
+ mSwitch.setOnCheckedChangeListener(null);
+ mSwitch = switch_;
+ mSwitch.setOnCheckedChangeListener(this);
+
+ final int wifiState = mWifiManager.getWifiState();
+ boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED;
+ boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED;
+ mSwitch.setChecked(isEnabled);
+ mSwitch.setEnabled(isEnabled || isDisabled);
+ }
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Show toast message if Wi-Fi is not allowed in airplane mode
- if (enable && !WirelessSettings
- .isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) {
- Toast.makeText(mContext, R.string.wifi_in_airplane_mode,
- Toast.LENGTH_SHORT).show();
- return false;
+ if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) {
+ Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
+ // Reset switch to off. No infinite check/listenenr loop.
+ buttonView.setChecked(false);
}
- /**
- * Disable tethering if enabling Wifi
- */
+ // Disable tethering if enabling Wifi
int wifiApState = mWifiManager.getWifiApState();
- if (enable && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
+ if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
(wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
mWifiManager.setWifiApEnabled(null, false);
}
- if (mWifiManager.setWifiEnabled(enable)) {
- mCheckBox.setEnabled(false);
+
+ if (mWifiManager.setWifiEnabled(isChecked)) {
+ // Intent has been taken into account, disable until new state is active
+ mSwitch.setEnabled(false);
} else {
- mCheckBox.setSummary(R.string.wifi_error);
+ // Error
+ Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
}
-
- // Don't update UI to opposite state until we're sure
- return false;
}
private void handleWifiStateChanged(int state) {
switch (state) {
case WifiManager.WIFI_STATE_ENABLING:
- mCheckBox.setSummary(R.string.wifi_starting);
- mCheckBox.setEnabled(false);
+ mSwitch.setEnabled(false);
break;
case WifiManager.WIFI_STATE_ENABLED:
- mCheckBox.setChecked(true);
- mCheckBox.setSummary(null);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(true);
+ mSwitch.setEnabled(true);
break;
case WifiManager.WIFI_STATE_DISABLING:
- mCheckBox.setSummary(R.string.wifi_stopping);
- mCheckBox.setEnabled(false);
+ mSwitch.setEnabled(false);
break;
case WifiManager.WIFI_STATE_DISABLED:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(mOriginalSummary);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(true);
break;
default:
- mCheckBox.setChecked(false);
- mCheckBox.setSummary(R.string.wifi_error);
- mCheckBox.setEnabled(true);
+ mSwitch.setChecked(false);
+ mSwitch.setEnabled(true);
}
}
- private void handleStateChanged(NetworkInfo.DetailedState state) {
+ private void handleStateChanged(@SuppressWarnings("unused") NetworkInfo.DetailedState state) {
+ // After the refactoring from a CheckBoxPreference to a Switch, this method is useless since
+ // there is nowhere to display a summary.
+ // This code is kept in case a future change re-introduces an associated text.
+ /*
// WifiInfo is valid if and only if Wi-Fi is enabled.
- // Here we use the state of the check box as an optimization.
- if (state != null && mCheckBox.isChecked()) {
+ // Here we use the state of the switch as an optimization.
+ if (state != null && mSwitch.isChecked()) {
WifiInfo info = mWifiManager.getConnectionInfo();
if (info != null) {
- mCheckBox.setSummary(Summary.get(mContext, info.getSSID(), state));
+ //setSummary(Summary.get(mContext, info.getSSID(), state));
}
}
+ */
}
}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index ab5e686..74400d4 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -18,6 +18,7 @@ package com.android.settings.wifi;
import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -31,40 +32,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 +80,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
* other decorations specific to that screen.
*/
public class WifiSettings extends SettingsPreferenceFragment
- implements DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener {
+ implements DialogInterface.OnClickListener {
private static final String TAG = "WifiSettings";
private static final int MENU_ID_SCAN = Menu.FIRST;
- private static final int MENU_ID_ADVANCED = Menu.FIRST + 1;
- private static final int MENU_ID_CONNECT = Menu.FIRST + 2;
- private static final int MENU_ID_FORGET = Menu.FIRST + 3;
- private static final int MENU_ID_MODIFY = Menu.FIRST + 4;
- private static final String KEY_SLEEP_POLICY = "sleep_policy";
+ private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 1;
+ private static final int MENU_ID_ADVANCED = Menu.FIRST + 2;
+ private static final int MENU_ID_CONNECT = Menu.FIRST + 3;
+ private static final int MENU_ID_FORGET = Menu.FIRST + 4;
+ private static final int MENU_ID_MODIFY = Menu.FIRST + 5;
private final IntentFilter mFilter;
private final BroadcastReceiver mReceiver;
@@ -98,12 +95,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 +107,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
@@ -157,11 +153,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
@@ -180,60 +173,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 +229,11 @@ public class WifiSettings extends SettingsPreferenceFragment
getActivity().registerReceiver(mReceiver, mFilter);
if (mKeyStoreNetworkId != INVALID_NETWORK_ID &&
- KeyStore.getInstance().test() == KeyStore.NO_ERROR) {
+ KeyStore.getInstance().state() == KeyStore.State.UNLOCKED) {
mWifiManager.connectNetwork(mKeyStoreNetworkId);
}
mKeyStoreNetworkId = INVALID_NETWORK_ID;
+
updateAccessPoints();
}
@@ -270,10 +255,18 @@ public class WifiSettings extends SettingsPreferenceFragment
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// We don't want menus in Setup Wizard XL.
if (!mInXlSetupWizard) {
+ final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
- .setIcon(R.drawable.ic_menu_scan_network);
+ //.setIcon(R.drawable.ic_menu_scan_network)
+ .setEnabled(wifiIsEnabled)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
+ //.setIcon(android.R.drawable.ic_menu_add)
+ .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);
}
@@ -286,15 +279,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 +361,17 @@ public class WifiSettings extends SettingsPreferenceFragment
if (preference instanceof AccessPoint) {
mSelectedAccessPoint = (AccessPoint) preference;
showConfigUi(mSelectedAccessPoint, false);
- } else if (preference == mAddNetwork) {
- onAddNetworkPressed();
- } else if (preference == mNotifyOpenNetworks) {
- Secure.putInt(getContentResolver(),
- Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- mNotifyOpenNetworks.isChecked() ? 1 : 0);
} else {
return super.onPreferenceTreeClick(screen, preference);
}
return true;
}
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- String key = preference.getKey();
- if (key == null) return true;
-
- if (key.equals(KEY_SLEEP_POLICY)) {
- try {
- Settings.System.putInt(getContentResolver(),
- Settings.System.WIFI_SLEEP_POLICY, Integer.parseInt(((String) newValue)));
- } catch (NumberFormatException e) {
- Toast.makeText(getActivity(), R.string.wifi_setting_sleep_policy_error,
- Toast.LENGTH_SHORT).show();
- return false;
- }
- }
-
- return true;
- }
-
-
/**
* Shows an appropriate Wifi configuration component.
* Called when a user clicks "Add network" preference or one of available networks is selected.
*/
private void showConfigUi(AccessPoint accessPoint, boolean edit) {
- mEdit = edit;
if (mInXlSetupWizard) {
((WifiSettingsForSetupWizardXL)getActivity()).showConfigUi(accessPoint, edit);
} else {
@@ -417,7 +389,7 @@ public class WifiSettings extends SettingsPreferenceFragment
private boolean requireKeyStore(WifiConfiguration config) {
if (WifiConfigController.requireKeyStore(config) &&
- KeyStore.getInstance().test() != KeyStore.NO_ERROR) {
+ KeyStore.getInstance().state() != KeyStore.State.UNLOCKED) {
mKeyStoreNetworkId = config.networkId;
Credentials.getInstance().unlock(getActivity());
return true;
@@ -430,20 +402,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 +536,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 +551,23 @@ 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;
}
+
+ mScanner.pause();
}
private class Scanner extends Handler {
@@ -580,7 +585,6 @@ public class WifiSettings extends SettingsPreferenceFragment
void pause() {
mRetry = 0;
- mAccessPoints.setProgress(false);
removeMessages(0);
}
@@ -594,7 +598,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 +639,7 @@ public class WifiSettings extends SettingsPreferenceFragment
}
break;
}
+ break;
//TODO: more connectivity feedback
default:
//Ignore
@@ -740,7 +744,7 @@ public class WifiSettings extends SettingsPreferenceFragment
mScanner.resume();
}
- mAccessPoints.removeAll();
+ getPreferenceScreen().removeAll();
}
/**
@@ -753,8 +757,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.
*/