summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/settings/AirplaneModeEnabler.java7
-rw-r--r--src/com/android/settings/ApnEditor.java10
-rw-r--r--src/com/android/settings/ApnSettings.java17
-rw-r--r--src/com/android/settings/ApplicationSettings.java15
-rw-r--r--src/com/android/settings/BatteryInfo.java2
-rw-r--r--src/com/android/settings/BluetoothDataEntry.java93
-rw-r--r--src/com/android/settings/BluetoothListItem.java72
-rw-r--r--src/com/android/settings/BluetoothPINEntry.java77
-rw-r--r--src/com/android/settings/BluetoothPinRequest.java80
-rw-r--r--src/com/android/settings/BluetoothSettings.java1035
-rw-r--r--src/com/android/settings/ChooseLockPattern.java16
-rw-r--r--src/com/android/settings/ChooseLockPatternExample.java14
-rw-r--r--src/com/android/settings/ChooseLockPatternTutorial.java19
-rw-r--r--src/com/android/settings/DateTimeSettings.java6
-rw-r--r--src/com/android/settings/DevelopmentSettings.java18
-rw-r--r--src/com/android/settings/DeviceInfoSettings.java5
-rw-r--r--src/com/android/settings/InputMethodsSettings.java191
-rw-r--r--src/com/android/settings/InstalledAppDetails.java315
-rw-r--r--src/com/android/settings/LocalePicker.java83
-rw-r--r--src/com/android/settings/ManageApplications.java1321
-rw-r--r--src/com/android/settings/ProxySelector.java2
-rw-r--r--src/com/android/settings/RadioInfo.java42
-rw-r--r--src/com/android/settings/RingerVolumePreference.java129
-rw-r--r--src/com/android/settings/SecuritySettings.java22
-rw-r--r--src/com/android/settings/SettingsLicenseActivity.java24
-rw-r--r--src/com/android/settings/SoundAndDisplaySettings.java39
-rw-r--r--src/com/android/settings/WirelessSettings.java146
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDevicePreference.java122
-rw-r--r--src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java192
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEnabler.java149
-rw-r--r--src/com/android/settings/bluetooth/BluetoothEventRedirector.java159
-rw-r--r--src/com/android/settings/bluetooth/BluetoothNamePreference.java79
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPinDialog.java112
-rw-r--r--src/com/android/settings/bluetooth/BluetoothPinRequest.java96
-rw-r--r--src/com/android/settings/bluetooth/BluetoothSettings.java258
-rw-r--r--src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java297
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothDevice.java558
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java209
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothManager.java260
-rw-r--r--src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java312
-rw-r--r--src/com/android/settings/bluetooth/SettingsBtStatus.java85
-rw-r--r--src/com/android/settings/deviceinfo/Memory.java28
-rw-r--r--src/com/android/settings/deviceinfo/Status.java6
-rw-r--r--src/com/android/settings/quicklaunch/QuickLaunchSettings.java2
-rw-r--r--src/com/android/settings/wifi/AccessPointDialog.java11
-rw-r--r--src/com/android/settings/wifi/AccessPointState.java16
-rw-r--r--src/com/android/settings/wifi/IpSettings.java86
-rw-r--r--src/com/android/settings/wifi/WifiLayer.java6
-rw-r--r--src/com/android/settings/wifi/WifiSettings.java10
49 files changed, 4689 insertions, 2164 deletions
diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java
index f47d6791d1..0bd950b212 100644
--- a/src/com/android/settings/AirplaneModeEnabler.java
+++ b/src/com/android/settings/AirplaneModeEnabler.java
@@ -100,9 +100,10 @@ public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListene
*/
private void onAirplaneModeChanged() {
ServiceState serviceState = mPhoneStateReceiver.getServiceState();
- boolean isPhoneOff = serviceState.getState() == ServiceState.STATE_POWER_OFF;
- mCheckBoxPref.setChecked(isPhoneOff);
- mCheckBoxPref.setSummary(R.string.airplane_mode_summary);
+ boolean airplaneModeEnabled = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+ mCheckBoxPref.setChecked(airplaneModeEnabled);
+ mCheckBoxPref.setSummary(airplaneModeEnabled ? null :
+ mContext.getString(R.string.airplane_mode_summary));
mCheckBoxPref.setEnabled(true);
}
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java
index 9b74eea65d..f1fa2efd00 100644
--- a/src/com/android/settings/ApnEditor.java
+++ b/src/com/android/settings/ApnEditor.java
@@ -63,6 +63,8 @@ public class ApnEditor extends PreferenceActivity
private EditTextPreference mMmsProxy;
private EditTextPreference mMmsPort;
private EditTextPreference mApnType;
+ private String mCurMnc;
+ private String mCurMcc;
private Uri mUri;
private Cursor mCursor;
@@ -210,6 +212,8 @@ public class ApnEditor extends PreferenceActivity
// Auto populate MNC and MCC for new entries, based on what SIM reports
mMcc.setText(mcc);
mMnc.setText(mnc);
+ mCurMnc = mnc;
+ mCurMcc = mcc;
}
}
}
@@ -338,6 +342,12 @@ public class ApnEditor extends PreferenceActivity
values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
+ if (mCurMnc != null && mCurMcc != null) {
+ if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) {
+ values.put(Telephony.Carriers.CURRENT, 1);
+ }
+ }
+
getContentResolver().update(mUri, values, null, null);
return true;
diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java
index ea13ee7731..83efa3f4bb 100644
--- a/src/com/android/settings/ApnSettings.java
+++ b/src/com/android/settings/ApnSettings.java
@@ -27,6 +27,7 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.provider.Telephony;
+import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
@@ -68,12 +69,16 @@ public class ApnSettings extends PreferenceActivity {
String name = mCursor.getString(NAME_INDEX);
String apn = mCursor.getString(APN_INDEX);
- Preference pref = new Preference((Context) this);
- pref.setKey(mCursor.getString(ID_INDEX));
- pref.setTitle(name);
- pref.setSummary(apn);
- pref.setPersistent(false);
- apnList.addPreference(pref);
+ if (name != null && apn != null && TextUtils.getTrimmedLength(name) > 0
+ && TextUtils.getTrimmedLength(apn) > 0) {
+ Preference pref = new Preference((Context) this);
+ pref.setKey(mCursor.getString(ID_INDEX));
+ pref.setTitle(name);
+ pref.setSummary(apn);
+ pref.setPersistent(false);
+ apnList.addPreference(pref);
+ }
+
mCursor.moveToNext();
}
}
diff --git a/src/com/android/settings/ApplicationSettings.java b/src/com/android/settings/ApplicationSettings.java
index 0ee2789d12..85fe11fcb7 100644
--- a/src/com/android/settings/ApplicationSettings.java
+++ b/src/com/android/settings/ApplicationSettings.java
@@ -18,6 +18,7 @@ package com.android.settings;
import android.app.AlertDialog;
import android.content.DialogInterface;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
@@ -29,6 +30,7 @@ public class ApplicationSettings extends PreferenceActivity implements
DialogInterface.OnClickListener {
private static final String KEY_TOGGLE_INSTALL_APPLICATIONS = "toggle_install_applications";
+ private static final String KEY_QUICK_LAUNCH = "quick_launch";
private CheckBoxPreference mToggleAppInstallation;
@@ -42,7 +44,12 @@ public class ApplicationSettings extends PreferenceActivity implements
mToggleAppInstallation = (CheckBoxPreference) findPreference(KEY_TOGGLE_INSTALL_APPLICATIONS);
mToggleAppInstallation.setChecked(isNonMarketAppsAllowed());
-
+
+ if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) {
+ // No hard keyboard, remove the setting for quick launch
+ Preference quickLaunchSetting = findPreference(KEY_QUICK_LAUNCH);
+ getPreferenceScreen().removePreference(quickLaunchSetting);
+ }
}
@Override
@@ -68,13 +75,13 @@ public class ApplicationSettings extends PreferenceActivity implements
private void setNonMarketAppsAllowed(boolean enabled) {
// Change the system setting
- Settings.System.putInt(getContentResolver(), Settings.System.INSTALL_NON_MARKET_APPS,
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS,
enabled ? 1 : 0);
}
private boolean isNonMarketAppsAllowed() {
- return Settings.System.getInt(getContentResolver(),
- Settings.System.INSTALL_NON_MARKET_APPS, 0) > 0;
+ return Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
}
private void warnAppInstallation() {
diff --git a/src/com/android/settings/BatteryInfo.java b/src/com/android/settings/BatteryInfo.java
index ef60fc35b1..ed3e3e3292 100644
--- a/src/com/android/settings/BatteryInfo.java
+++ b/src/com/android/settings/BatteryInfo.java
@@ -29,7 +29,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.pim.DateUtils;
+import android.text.format.DateUtils;
import android.widget.TextView;
import com.android.internal.app.IBatteryStats;
diff --git a/src/com/android/settings/BluetoothDataEntry.java b/src/com/android/settings/BluetoothDataEntry.java
deleted file mode 100644
index 1c7e0b6bd4..0000000000
--- a/src/com/android/settings/BluetoothDataEntry.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnKeyListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-public class BluetoothDataEntry extends Activity implements OnKeyListener {
-
- private Bundle extras;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- setContentView(R.layout.bluetooth_data_entry);
-
- mDataLabel = (TextView)findViewById(R.id.dataLabel);
- mDataEntry = (EditText)findViewById(R.id.dataEntry);
- mConfirmButton = (Button)findViewById(R.id.confirmButton);
- mCancelButton = (Button)findViewById(R.id.cancelButton);
-
- mDataEntry.setOnKeyListener(this);
- Intent intent = getIntent();
- String label = null;
- {
- String labelExtra = intent.getStringExtra("label");
- if (labelExtra != null) {
- label = labelExtra;
- }
- }
- extras = intent.getBundleExtra("extras");
- if (label != null && label.length() > 0) {
- mDataLabel.setText(label);
- }
-
- mConfirmButton.setOnClickListener(new ConfirmButtonListener());
- mCancelButton.setOnClickListener(new CancelButtonListener());
- }
-
- private class ConfirmButtonListener implements OnClickListener {
- public void onClick(View v) {
- activityResult(RESULT_OK, mDataEntry.getText().toString(), extras);
- }
- }
-
- private class CancelButtonListener implements OnClickListener {
- public void onClick(View v) {
- activityResult(RESULT_CANCELED, null, null);
- }
- }
-
- protected void activityResult(int result, String data, Bundle extras) {
- setResult(result, (new Intent()).setAction(data).putExtras(extras));
- finish();
- }
-
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
- || keyCode == KeyEvent.KEYCODE_ENTER) {
- activityResult(RESULT_OK, mDataEntry.getText().toString(), extras);
- return true;
- }
- return false;
- }
-
- protected TextView mDataLabel;
- protected EditText mDataEntry;
- protected Button mConfirmButton;
- protected Button mCancelButton;
-}
diff --git a/src/com/android/settings/BluetoothListItem.java b/src/com/android/settings/BluetoothListItem.java
deleted file mode 100644
index c75be1374c..0000000000
--- a/src/com/android/settings/BluetoothListItem.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.android.settings;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import java.util.Map;
-
-/**
- * This class extends Preference to display bluetooth status icons. One
- * icon specifies the connection/pairing status that is right-aligned.
- * An optional headset icon can be added to its left as well.
- */
-public class BluetoothListItem extends Preference {
-
- private boolean mIsHeadset;
- private int mWeight;
-
- public BluetoothListItem(Context context, AttributeSet attrs) {
- super(context, attrs);
- setWidgetLayoutResource(R.layout.preference_widget_btdevice_status);
- }
-
- private void updateIcons(View view) {
- ImageView headsetView = (ImageView) view.findViewById(R.id.device_headset);
- headsetView.setVisibility(mIsHeadset ? View.VISIBLE : View.GONE);
- }
-
- @Override
- public void onBindView(View view) {
- super.onBindView(view);
- updateIcons(view);
- }
-
- /**
- * Set whether the device is of headset type
- * @param headset whether or not the headset icon should be shown
- */
- public void setHeadset(boolean headset) {
- mIsHeadset = headset;
- notifyChanged();
- }
-
- /**
- * Sets the weight for ordering by signal strength or importance
- * @param weight the ordering weight
- */
- public void setWeight(int weight) {
- mWeight = weight;
- }
-
- /**
- * Returns the currently set ordering weight
- * @return the current ordering weight
- */
- public int getWeight() {
- return mWeight;
- }
-
- @Override
- public int compareTo(Preference another) {
- int diff = ((BluetoothListItem)another).mWeight - mWeight;
- // Let the new one be after the old one, if they are the same weight
- // TODO: Implement a more reliable way to consistently order items of
- // the same weight
- if (diff == 0) diff = 1;
- return diff;
- }
-}
diff --git a/src/com/android/settings/BluetoothPINEntry.java b/src/com/android/settings/BluetoothPINEntry.java
deleted file mode 100644
index 8933c03c43..0000000000
--- a/src/com/android/settings/BluetoothPINEntry.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.NotificationManager;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-public class BluetoothPINEntry extends BluetoothDataEntry {
- private BluetoothDevice mBluetooth;
- private String mAddress;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- Intent intent = getIntent();
- if (!intent.getAction().equals(BluetoothIntent.PAIRING_REQUEST_ACTION))
- {
- Log.e(this.getClass().getName(),
- "Error: this activity may be started only with intent " +
- BluetoothIntent.PAIRING_REQUEST_ACTION);
- finish();
- }
-
- // Cancel the notification, if any
- NotificationManager manager = (NotificationManager)
- getSystemService(Context.NOTIFICATION_SERVICE);
- manager.cancel(0xb100ceee);
-
- mAddress = intent.getStringExtra(BluetoothIntent.ADDRESS);
-
- mBluetooth = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
-
- String remoteName = mBluetooth.getRemoteName(mAddress);
- if (remoteName == null) {
- remoteName = mAddress;
- }
-
- mDataLabel.setText(getString(R.string.bluetooth_enter_pin_msg) + remoteName);
- }
-
- @Override
- public void activityResult(int result, String data, Bundle extras) {
- switch (result) {
- case RESULT_OK:
- byte[] pin = BluetoothDevice.convertPinToBytes(mDataEntry.getText().toString());
- if (pin == null) {
- return;
- }
- mBluetooth.setPin(mAddress, pin);
- break;
- case RESULT_CANCELED:
- mBluetooth.cancelPin(mAddress);
- break;
- }
- finish();
- }
-}
diff --git a/src/com/android/settings/BluetoothPinRequest.java b/src/com/android/settings/BluetoothPinRequest.java
deleted file mode 100644
index d6a12f3efb..0000000000
--- a/src/com/android/settings/BluetoothPinRequest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-
-/**
- * This class handles the Bluetooth pairing PIN request from the bluetooth service
- * It checks if the BluetoothSettings activity is currently visible and lets that
- * activity handle the request. Otherwise it puts a Notification in the status bar,
- * which can be clicked to bring up the PIN entry dialog.
- */
-public class BluetoothPinRequest extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
- if (BluetoothSettings.isRunning()) {
- // Let the BluetoothSettings activity handle it
- return;
- } else {
- Resources res = context.getResources();
- String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
- Notification pair = new Notification(
- android.R.drawable.stat_sys_data_bluetooth,
- res.getString(R.string.bluetooth_notif_ticker),
- System.currentTimeMillis());
-
- Intent pinIntent = new Intent();
- pinIntent.setClass(context, BluetoothPINEntry.class);
- pinIntent.putExtra(BluetoothIntent.ADDRESS, address);
- pinIntent.setAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
- PendingIntent pending = PendingIntent.getActivity(context, 0,
- pinIntent, PendingIntent.FLAG_ONE_SHOT);
-
- String name = intent.getStringExtra(BluetoothIntent.NAME);
-
- if (name == null) {
- BluetoothDevice bluetooth =
- (BluetoothDevice)context.getSystemService(Context.BLUETOOTH_SERVICE);
- name = bluetooth.getRemoteName(address);
- if (name == null) {
- name = address;
- }
- }
-
- pair.setLatestEventInfo(context,
- res.getString(R.string.bluetooth_notif_title),
- res.getString(R.string.bluetooth_notif_message) + name,
- pending);
-
- NotificationManager manager = (NotificationManager)
- context.getSystemService(Context.NOTIFICATION_SERVICE);
- manager.notify(0xb100ceee, pair);
- }
- }
- }
-}
diff --git a/src/com/android/settings/BluetoothSettings.java b/src/com/android/settings/BluetoothSettings.java
deleted file mode 100644
index 6a19ec4c7f..0000000000
--- a/src/com/android/settings/BluetoothSettings.java
+++ /dev/null
@@ -1,1035 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.AlertDialog;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothIntent;
-import android.bluetooth.DeviceClass;
-import android.bluetooth.IBluetoothDeviceCallback;
-import android.bluetooth.IBluetoothHeadsetCallback;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.text.method.PasswordTransformationMethod;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnKeyListener;
-import android.widget.AdapterView;
-import android.widget.EditText;
-import android.widget.Toast;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-import java.util.HashMap;
-
-public class BluetoothSettings
- extends PreferenceActivity
- implements OnSharedPreferenceChangeListener, OnKeyListener,
- View.OnCreateContextMenuListener {
-
- private static final String TAG = "BluetoothSettings";
-
- private static final int MENU_SCAN_ID = Menu.FIRST;
- private static final int MENU_CLEAR_ID = Menu.FIRST + 1;
-
- private static final int MENU_CONNECT = ContextMenu.FIRST;
- private static final int MENU_DISCONNECT = ContextMenu.FIRST + 1;
- private static final int MENU_PAIR = ContextMenu.FIRST + 2;
- private static final int MENU_UNPAIR = ContextMenu.FIRST + 3;
-
- private static final String BT_ENABLE = "bt_checkbox";
- private static final String BT_VISIBILITY = "bt_visibility";
- private static final String BT_NAME = "bt_name";
-
- private static final String BT_KEY_PREFIX = "bt_dev_";
- private static final int BT_KEY_LENGTH = BT_KEY_PREFIX.length();
- private static final String FREEZE_ADDRESSES = "addresses";
- private static final String FREEZE_TYPES = "types";
- private static final String FREEZE_PIN = "pinText";
- private static final String FREEZE_PIN_ADDRESS = "pinAddress";
- private static final String FREEZE_RSSI = "rssi";
- private static final String FREEZE_DISCOVERABLE_START = "dstart";
-
- private static final int HANDLE_FAILED_TO_CONNECT = 1;
- private static final int HANDLE_CONNECTING = 2;
- private static final int HANDLE_CONNECTED = 3;
- private static final int HANDLE_DISCONNECTED = 4;
- private static final int HANDLE_PIN_REQUEST = 5;
- private static final int HANDLE_DISCOVERABLE_TIMEOUT = 6;
- private static final int HANDLE_INITIAL_SCAN = 7;
- private static final int HANDLE_PAIRING_FAILED = 8;
- private static final int HANDLE_PAIRING_PASSED = 9;
- private static final int HANDLE_PAUSE_TIMEOUT = 10;
-
-
-
- private static String STR_CONNECTED;
- private static String STR_PAIRED;
- private static String STR_PAIRED_NOT_NEARBY;
- private static String STR_NOT_CONNECTED;
- private static String STR_CONNECTING;
- private static String STR_PAIRING;
-
- private static final int WEIGHT_CONNECTED = 1;
- private static final int WEIGHT_PAIRED = 0;
- private static final int WEIGHT_UNKNOWN = -1;
-
- private CheckBoxPreference mBTToggle;
- private CheckBoxPreference mBTVisibility;
- private EditTextPreference mBTName;
- private ProgressCategory mBTDeviceList;
- private AlertDialog mPinDialog;
- private String mPinAddress;
- private EditText mPinEdit;
- private View mPinButton1;
- private String mDisconnectAddress;
-
- private BluetoothDevice mBluetooth;
- private BluetoothHeadset mBluetoothHeadset;
- private boolean mIsEnabled;
- private String mLastConnected;
- private static boolean sIsRunning;
- private static DeviceCallback sDeviceCallback;
- private IntentFilter mIntentFilter;
- private Resources mRes;
- private long mDiscoverableStartTime;
- private int mDiscoverableTime;
- private static final String DISCOVERABLE_TIME = "debug.bt.discoverable_time";
- private static final int DISCOVERABLE_TIME_DEFAULT = 120;
- private boolean mAutoDiscovery;
- // After a few seconds after a pause, if the user doesn't restart the
- // BT settings, then we need to cleanup a few things in the message handler
- private static final int PAUSE_TIMEOUT = 3000;
-
- private boolean mStartScan;
- private static final String AUTO_DISCOVERY = "debug.bt.auto_discovery";
- private HashMap<String,Preference> mDeviceMap;
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- addPreferencesFromResource(R.xml.bluetooth_settings);
-
- // Deal with restarted activities by passing a static callback object to
- // the Bluetooth service
- if (sDeviceCallback == null) {
- sDeviceCallback = new DeviceCallback();
- }
- sDeviceCallback.setHandler(mHandler);
-
- mDiscoverableTime = SystemProperties.getInt(DISCOVERABLE_TIME, -1);
- if (mDiscoverableTime <= 0) {
- mDiscoverableTime= DISCOVERABLE_TIME_DEFAULT;
- }
- mAutoDiscovery = SystemProperties.getBoolean(AUTO_DISCOVERY, true);
-
- if (!initBluetoothAPI()) {
- finish();
- return;
- }
- initUI();
- getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
-
- mDeviceMap = new HashMap<String,Preference>();
- if (icicle != null && icicle.containsKey(FREEZE_ADDRESSES)) {
- addDevices(icicle.getStringArray(FREEZE_ADDRESSES),
- icicle.getStringArray(FREEZE_TYPES),
- icicle.getIntArray(FREEZE_RSSI));
- if (icicle.containsKey(FREEZE_PIN)) {
- String savedPin = icicle.getString(FREEZE_PIN);
- String pinAddress = icicle.getString(FREEZE_PIN_ADDRESS);
- mPinDialog = showPinDialog(savedPin, pinAddress);
- }
- mDiscoverableStartTime = icicle.getLong(FREEZE_DISCOVERABLE_START);
- } else {
- mStartScan = true;
- }
- }
-
- private void initUI() {
- mBTToggle = (CheckBoxPreference) findPreference(BT_ENABLE);
- mBTVisibility = (CheckBoxPreference) findPreference(BT_VISIBILITY);
- mBTName = (EditTextPreference) findPreference(BT_NAME);
- mBTDeviceList = (ProgressCategory) findPreference("bt_device_list");
- mBTDeviceList.setOrderingAsAdded(false);
- mRes = getResources();
- if (mIsEnabled) {
- String name = mBluetooth.getName();
- if (name != null) {
- mBTName.setSummary(name);
- }
- }
- mBTVisibility.setEnabled(mIsEnabled);
- mBTName.setEnabled(mIsEnabled);
- STR_CONNECTED = mRes.getString(R.string.bluetooth_connected);
- STR_PAIRED = mRes.getString(R.string.bluetooth_paired);
- STR_PAIRED_NOT_NEARBY =
- mRes.getString(R.string.bluetooth_paired_not_nearby);
- STR_CONNECTING = mRes.getString(R.string.bluetooth_connecting);
- STR_PAIRING = mRes.getString(R.string.bluetooth_pairing);
- STR_NOT_CONNECTED = mRes.getString(R.string.bluetooth_not_connected);
- getListView().setOnCreateContextMenuListener(this);
- }
-
- private boolean initBluetoothAPI() {
- mIntentFilter =
- new IntentFilter(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.BONDING_CREATED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.ENABLED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
- mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
- mIntentFilter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.MODE_CHANGED_ACTION);
-
- mBluetooth = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
- mBluetoothHeadset = new BluetoothHeadset(this);
- if (mBluetooth == null) { // If the environment doesn't support BT
- return false;
- }
- mIsEnabled = mBluetooth.isEnabled();
- return true;
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- sIsRunning = true;
- mHandler.removeMessages(HANDLE_PAUSE_TIMEOUT);
- registerReceiver(mReceiver, mIntentFilter);
-
- mIsEnabled = mBluetooth.isEnabled();
- updateStatus();
- final boolean discoverable = mBluetooth.getMode() ==
- BluetoothDevice.MODE_DISCOVERABLE;
- mBTDeviceList.setProgress(mIsEnabled && mBluetooth.isDiscovering());
- mBTVisibility.setChecked(mIsEnabled && discoverable);
-
- if (discoverable) {
- mHandler.sendMessage(
- mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT));
- }
-
- if (mIsEnabled && mStartScan) {
- // First attempt after 100ms
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(HANDLE_INITIAL_SCAN, 1), 100);
- }
- mStartScan = false;
-
- // Check if headset status changed since we paused
- String connected = mBluetoothHeadset.getHeadsetAddress();
- if (connected != null) {
- updateRemoteDeviceStatus(connected);
- }
- if (mLastConnected != null) {
- updateRemoteDeviceStatus(mLastConnected);
- }
- }
-
- @Override
- protected void onPause() {
- sIsRunning = false;
-
- unregisterReceiver(mReceiver);
-
- // Wait for a few seconds and cleanup any pending requests, states
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(HANDLE_PAUSE_TIMEOUT,
- new Object[] { mBluetooth, mPinAddress }),
- PAUSE_TIMEOUT);
- super.onPause();
- }
-
- @Override
- protected void onDestroy() {
- mBluetoothHeadset.close();
- sDeviceCallback.setHandler(null);
-
- super.onDestroy();
- }
-
- @Override
- public void onConfigurationChanged(Configuration c) {
- super.onConfigurationChanged(c);
- // Don't do anything on keyboardHidden/orientation change, as we need
- // to make sure that we don't lose pairing request intents.
- }
-
- public static boolean isRunning() {
- return sIsRunning;
- }
-
- @Override
- protected void onSaveInstanceState(Bundle icicle) {
- int deviceCount = mBTDeviceList.getPreferenceCount();
- String [] addresses = new String[deviceCount];
- String [] states = new String[deviceCount];
- int [] weights = new int[deviceCount];
- for (int i = 0; i < deviceCount; i++) {
- BluetoothListItem p = (BluetoothListItem) mBTDeviceList.getPreference(i);
- CharSequence summary = p.getSummary();
- if (summary != null) {
- states[i] = summary.toString();
- } else {
- states[i] = STR_NOT_CONNECTED;
- }
- addresses[i] = getAddressFromKey(p.getKey());
- weights[i] = p.getWeight();
- }
- icicle.putStringArray(FREEZE_ADDRESSES, addresses);
- icicle.putStringArray(FREEZE_TYPES, states);
- icicle.putIntArray(FREEZE_RSSI, weights);
- icicle.putLong(FREEZE_DISCOVERABLE_START, mDiscoverableStartTime);
- if (mPinDialog != null && mPinDialog.isShowing()) {
- icicle.putString(FREEZE_PIN, mPinEdit.getText().toString());
- icicle.putString(FREEZE_PIN_ADDRESS, mPinAddress);
- }
- super.onSaveInstanceState(icicle);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- menu.add(0, MENU_SCAN_ID, 0,
- mRes.getString(R.string.bluetooth_scan_for_devices))
- .setIcon(R.drawable.ic_menu_scan_bluetooth);
- menu.add(0, MENU_CLEAR_ID, 0,
- mRes.getString(R.string.bluetooth_clear_list))
- .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_SCAN_ID:
- startScanning();
- return true;
- case MENU_CLEAR_ID:
- clearDevices();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- if (!(menuInfo instanceof AdapterContextMenuInfo)) {
- return;
- }
- int position = ((AdapterContextMenuInfo)menuInfo).position;
- Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position);
- if (!(pref instanceof BluetoothListItem)) {
- return;
- }
- String address = getAddressFromKey(pref.getKey());
- // Setup the menu header
- String name = mBluetooth.getRemoteName(address);
- menu.setHeaderTitle(name != null? name : address);
- int n = 0;
- if (mBluetoothHeadset.isConnected(address)) {
- menu.add(0, MENU_DISCONNECT, n++, R.string.bluetooth_disconnect);
- } else {
- menu.add(0, MENU_CONNECT, n++, R.string.bluetooth_connect);
- }
- if (mBluetooth.hasBonding(address)) {
- menu.add(0, MENU_UNPAIR, n++, R.string.bluetooth_unpair);
- } else {
- menu.add(0, MENU_PAIR, n++, R.string.bluetooth_pair);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- AdapterView.AdapterContextMenuInfo info;
- if (!(item.getMenuInfo() instanceof AdapterContextMenuInfo)) {
- return false;
- }
- info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
- Preference pref = (Preference) getPreferenceScreen().getRootAdapter().
- getItem(info.position);
- String address = getAddressFromKey(pref.getKey());
- mBluetooth.cancelDiscovery();
- switch (item.getItemId()) {
- case MENU_DISCONNECT:
- if (mBluetoothHeadset.isConnected(address)) {
- mBluetoothHeadset.disconnectHeadset();
- }
- break;
- case MENU_CONNECT:
- if (!mBluetoothHeadset.isConnected(address)) {
- updateRemoteDeviceStatus(address, STR_CONNECTING);
- connect(pref, address);
- }
- break;
- case MENU_UNPAIR:
- if (mBluetooth.hasBonding(address)) {
- mBluetooth.removeBonding(address);
- updateRemoteDeviceStatus(address);
- }
- break;
- case MENU_PAIR:
- if (!mBluetooth.hasBonding(address)) {
- pair(pref, address);
- }
- break;
- }
- return true;
- }
-
- private void startScanning() {
- if (mIsEnabled && mBluetooth.isDiscovering()) {
- return;
- }
- resetDeviceListUI();
- if (mIsEnabled) {
- mBluetooth.startDiscovery();
- }
- }
-
- private void clearDevices() {
- String [] addresses = mBluetooth.listBondings();
- if (addresses != null) {
- for (int i = 0; i < addresses.length; i++) {
- unbond(addresses[i]);
- }
- }
- resetDeviceListUI();
- }
-
- /* Update the Bluetooth toggle and visibility summary */
- private void updateStatus() {
- boolean started = mIsEnabled;
- mBTToggle.setChecked(started);
- }
-
- private void updateRemoteDeviceStatus(String address) {
- if (address != null) {
- Preference device = mDeviceMap.get(address);
- if (device == null) {
- // This device is not in our discovered list
- // Let's add the device, if BT is not shut down already
- if (mIsEnabled) {
- addDeviceToUI(address, null, null, WEIGHT_PAIRED);
- }
- return;
- }
- device.setEnabled(true);
- if (address.equals(mBluetoothHeadset.getHeadsetAddress())) {
- int state = mBluetoothHeadset.getState();
- switch (state) {
- case BluetoothHeadset.STATE_CONNECTED:
- device.setSummary(STR_CONNECTED);
- mLastConnected = address;
- break;
- case BluetoothHeadset.STATE_CONNECTING:
- device.setSummary(STR_CONNECTING);
- break;
- case BluetoothHeadset.STATE_DISCONNECTED:
- if (mBluetooth.hasBonding(address)) {
- device.setSummary(STR_PAIRED);
- }
- break;
- }
- } else if (mBluetooth.hasBonding(address)) {
- device.setSummary(STR_PAIRED);
- } else {
- device.setSummary(STR_NOT_CONNECTED);
- }
- }
- }
-
- private void updateRemoteDeviceStatus(String address, String summary) {
- Preference device = mDeviceMap.get(address);
- if (device != null) {
- device.setEnabled(true);
- device.setSummary(summary);
- }
- }
-
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (key.equals(BT_NAME)) {
- String name = sharedPreferences.getString(key, null);
- if (name == null) {
- return;
- }
- if (mBluetooth.setName(name)) {
- mBTName.setSummary(name);
- }
- }
- }
-
- private String getAddressFromKey(String key) {
- if (key != null) {
- return key.substring(BT_KEY_LENGTH);
- }
- return "";
- }
-
- private void sendPin(String pin) {
- byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
- if (pinBytes == null) {
- mBluetooth.cancelPin(mPinAddress);
- } else {
- mBluetooth.setPin(mPinAddress, pinBytes);
- }
- mPinAddress = null;
- }
-
- private AlertDialog showPinDialog(String savedPin, String pinAddress) {
- if (mPinDialog != null) {
- return mPinDialog;
- }
- View view = LayoutInflater.from(this).inflate(
- R.layout.bluetooth_pin_entry, null);
- mPinEdit = (EditText) view.findViewById(R.id.text);
- mPinEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
- mPinEdit.setOnKeyListener(this);
- mPinAddress = pinAddress;
-
- if (savedPin != null) {
- mPinEdit.setText(savedPin);
- }
-
- String remoteName = mBluetooth.getRemoteName(mPinAddress);
- if (remoteName == null) {
- remoteName = mPinAddress;
- }
-
- AlertDialog ad = new AlertDialog.Builder(this)
- .setTitle(getString(R.string.bluetooth_notif_title))
- .setMessage(getString(R.string.bluetooth_enter_pin_msg) + remoteName)
- .setView(view)
- .setPositiveButton(android.R.string.ok, mDisconnectListener)
- .setNegativeButton(android.R.string.cancel, mDisconnectListener)
- .setOnCancelListener(mCancelListener)
- .show();
- ad.setCanceledOnTouchOutside(false);
- // Making an assumption here that the dialog buttons have the ids starting
- // with ...button1 as below
- mPinButton1 = ad.findViewById(com.android.internal.R.id.button1);
- if (mPinButton1 != null) {
- mPinButton1.setEnabled(savedPin != null? savedPin.length() > 0 : false);
- }
- return ad;
- }
-
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (preference == mBTToggle) {
- toggleBT();
- return false;
- } else if (preference == mBTVisibility) {
- boolean vis = mBTVisibility.isChecked();
- if (!vis) {
- // Cancel discoverability
- mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
- mHandler.removeMessages(HANDLE_DISCOVERABLE_TIMEOUT);
- } else {
- mBluetooth.setMode(BluetoothDevice.MODE_DISCOVERABLE);
- mBTVisibility.setSummaryOn(
- getResources().getString(R.string.bluetooth_is_discoverable,
- String.valueOf(mDiscoverableTime)));
- mDiscoverableStartTime = SystemClock.elapsedRealtime();
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
- }
- } else {
- String key = preference.getKey();
- if (key.startsWith(BT_KEY_PREFIX)) {
- // Extract the device address from the key
- String address = getAddressFromKey(key);
- if (mBluetoothHeadset.isConnected(address)) {
- askDisconnect(address);
- } else if (mBluetooth.hasBonding(address)) {
- if (mIsEnabled) {
- mBluetooth.cancelDiscovery();
- }
- updateRemoteDeviceStatus(address, STR_CONNECTING);
- connect(preference, address);
- } else {
- if (mIsEnabled) {
- mBluetooth.cancelDiscovery();
- }
- pair(preference, address);
- }
- }
- }
- return false;
- }
-
- /* Handle the key input to the PIN entry dialog */
- public boolean onKey(View v, int keyCode, KeyEvent event) {
-
- if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
- || keyCode == KeyEvent.KEYCODE_ENTER) {
- String pin = ((EditText)v).getText().toString();
- if (pin != null && pin.length() > 0) {
- sendPin(pin);
- mPinDialog.dismiss();
- return true;
- }
- } else if (mPinButton1 != null) {
- boolean valid =
- BluetoothDevice.convertPinToBytes(((EditText)v).getText().toString()) != null;
- mPinButton1.setEnabled(valid);
- }
- return false;
- }
-
- private void askDisconnect(String address) {
- String name = mBluetooth.getRemoteName(address);
- if (name == null) {
- name = mRes.getString(R.string.bluetooth_device);
- }
- String message = mRes.getString(R.string.bluetooth_disconnect_blank, name);
-
- mDisconnectAddress = address;
-
- AlertDialog ad = new AlertDialog.Builder(this)
- .setTitle(message)
- .setPositiveButton(android.R.string.ok, mDisconnectListener)
- .setNegativeButton(android.R.string.cancel, null)
- .show();
- ad.setCanceledOnTouchOutside(false);
-
- }
-
- private void pairingDone(String address, boolean result) {
- Preference pref = mDeviceMap.get(address);
- if (pref != null) {
- pref.setEnabled(true);
- updateRemoteDeviceStatus(address);
- } else if (result) {
- // We've paired to a device that isn't in our list
- addDeviceToUI(address, STR_PAIRED, mBluetooth.getRemoteName(address),
- WEIGHT_PAIRED);
- }
- }
-
- private void pair(Preference pref, String address) {
- pref.setEnabled(false);
- pref.setSummary(STR_PAIRING);
- mBluetooth.createBonding(address, sDeviceCallback);
- }
-
- private void connect(Preference pref, String address) {
- pref.setEnabled(false);
- //TODO: Prompt the user to confirm they will disconnect current headset
- disconnect();
- mBluetoothHeadset.connectHeadset(address, mHeadsetCallback);
- }
-
- private void disconnect() {
- int state = mBluetoothHeadset.getState();
- if (state == BluetoothHeadset.STATE_CONNECTING ||
- state == BluetoothHeadset.STATE_CONNECTED) {
- mBluetoothHeadset.disconnectHeadset();
- }
- }
-
- private void toggleBT() {
- if (mIsEnabled) {
- mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_stopping));
- mBTDeviceList.setProgress(false);
- // Force shutdown.
- mBluetooth.cancelDiscovery();
- mBluetooth.disable();
- } else {
- mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_enabling));
- mBTToggle.setChecked(false);
- mBTToggle.setEnabled(false);
- if (!mBluetooth.enable()) {
- mBTToggle.setEnabled(true);
- }
- }
- }
-
- private void addDeviceToUI(String address, String summary, String name,
- int rssi) {
-
- if (address == null) {
- return;
- }
-
- BluetoothListItem p;
- if (mDeviceMap.containsKey(address)) {
- p = (BluetoothListItem) mDeviceMap.get(address);
- if (summary != null && summary.equals(STR_NOT_CONNECTED)) {
- if (mBluetooth.hasBonding(address)) {
- summary = STR_PAIRED;
- }
- }
- CharSequence oldSummary = p.getSummary();
- if (oldSummary != null && oldSummary.equals(STR_CONNECTED)) {
- summary = STR_CONNECTED; // Don't override connected with paired
- mLastConnected = address;
- }
- } else {
- p = new BluetoothListItem(this, null);
- }
- if (name == null) {
- name = mBluetooth.getRemoteName(address);
- }
- if (name == null) {
- name = address;
- }
-
- p.setTitle(name);
- p.setSummary(summary);
- p.setKey(BT_KEY_PREFIX + address);
- // Enable the headset icon if it is most probably a headset class device
- if (DeviceClass.getMajorClass(mBluetooth.getRemoteClass(address)) ==
- DeviceClass.MAJOR_CLASS_AUDIO_VIDEO) {
- p.setHeadset(true);
- }
- p.setWeight(rssi);
- if (!mDeviceMap.containsKey(address)) {
- mBTDeviceList.addPreference(p);
- mDeviceMap.put(address, p);
- }
- }
-
- private void addDevices(String [] addresses,
- String[] deviceStatus, int[] rssi) {
- for (int i = 0; i < addresses.length; i++) {
- String status = deviceStatus[i];
- String name = mBluetooth.getRemoteName(addresses[i]);
- String address = addresses[i];
- // Query the status if it's not known
- if (status == null) {
- if (mBluetoothHeadset.isConnected(addresses[i])) {
- status = STR_CONNECTED;
- mLastConnected = address;
- } else if (mBluetooth.hasBonding(addresses[i])) {
- status = STR_PAIRED;
- } else {
- status = STR_NOT_CONNECTED;
- }
- }
- addDeviceToUI(address, status, name, rssi[i]);
- }
- }
-
- private void removeDeviceFromUI(String address) {
- Preference p = mDeviceMap.get(address);
- if (p == null) {
- return;
- }
- mBTDeviceList.removePreference(p);
- mDeviceMap.remove(address);
- }
-
- private void updateDeviceName(String address, String name) {
- Preference p = mDeviceMap.get(address);
- if (p != null) {
- p.setTitle(name);
- }
- }
-
- private void resetDeviceListUI() {
- mDeviceMap.clear();
-
- while (mBTDeviceList.getPreferenceCount() > 0) {
- mBTDeviceList.removePreference(mBTDeviceList.getPreference(0));
- }
- if (!mIsEnabled) {
- return;
- }
-
- String connectedDevice = mBluetoothHeadset.getHeadsetAddress();
- if (connectedDevice != null && mBluetoothHeadset.isConnected(connectedDevice)) {
- addDeviceToUI(connectedDevice, STR_CONNECTED,
- mBluetooth.getRemoteName(connectedDevice), WEIGHT_CONNECTED);
- }
- String [] bondedDevices = mBluetooth.listBondings();
- if (bondedDevices != null) {
- for (int i = 0; i < bondedDevices.length; i++) {
- addDeviceToUI(bondedDevices[i], STR_PAIRED_NOT_NEARBY,
- mBluetooth.getRemoteName(bondedDevices[i]), WEIGHT_PAIRED);
- }
- }
- }
-
- private void unbond(String address) {
- mBluetooth.removeBonding(address);
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
- if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
- mIsEnabled = true;
- mBTToggle.setChecked(true);
- mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_enabled));
- mBTToggle.setEnabled(true);
- String name = mBluetooth.getName();
- if (name != null) {
- mBTName.setSummary(name);
- }
- // save the "enabled" setting to database, so we can
- // remember it on startup.
- Settings.System.putInt(getContentResolver(),
- Settings.System.BLUETOOTH_ON, 1);
- resetDeviceListUI();
- if (mAutoDiscovery) {
- mBluetooth.startDiscovery();
- }
- } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
- mIsEnabled = false;
- mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_disabled));
- resetDeviceListUI();
- mBTVisibility.setChecked(false);
- // save the "disabled" setting to database
- Settings.System.putInt(getContentResolver(),
- Settings.System.BLUETOOTH_ON, 0);
- } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)) {
- if (address != null) {
- int rssi = intent.getShortExtra(BluetoothIntent.RSSI,
- (short) WEIGHT_UNKNOWN);
- addDeviceToUI(address, STR_NOT_CONNECTED, null, rssi);
- }
- } else if (action.equals(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION)) {
- String name = intent.getStringExtra(BluetoothIntent.NAME);
- updateDeviceName(address, name);
- } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION)) {
- removeDeviceFromUI(address);
- } else if (action.equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PIN_REQUEST, address));
- } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
- int state = intent.getIntExtra(BluetoothIntent.HEADSET_STATE,
- BluetoothHeadset.STATE_ERROR);
- if (state == BluetoothHeadset.STATE_CONNECTED) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
- } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
- } else if (state == BluetoothHeadset.STATE_CONNECTING) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTING, address));
- }
- } else if (action.equals(BluetoothIntent.DISCOVERY_STARTED_ACTION)) {
- mBTDeviceList.setProgress(true);
- } else if (action.equals(BluetoothIntent.DISCOVERY_COMPLETED_ACTION)) {
- mBTDeviceList.setProgress(false);
- } else if (action.equals(BluetoothIntent.MODE_CHANGED_ACTION)) {
- mBTVisibility.setChecked(
- mBluetooth.getMode() == BluetoothDevice.MODE_DISCOVERABLE);
- } else if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PAIRING_PASSED, address));
- } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
- } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION)) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
- }
- }
- };
-
-
- static class DeviceCallback extends IBluetoothDeviceCallback.Stub {
- Handler messageHandler;
-
- public void setHandler(Handler handler) {
- synchronized (this) {
- messageHandler = handler;
- }
- }
-
- public void onCreateBondingResult(String address, int result) {
- synchronized (this) {
- if (messageHandler != null) {
- if (result == BluetoothDevice.RESULT_FAILURE) {
- messageHandler.sendMessage(messageHandler.obtainMessage(
- HANDLE_PAIRING_FAILED, address));
- } else {
- messageHandler.sendMessage(messageHandler.obtainMessage(
- HANDLE_PAIRING_PASSED, address));
- }
- }
- }
- }
-
- public void onEnableResult(int result) { }
- public void onGetRemoteServiceChannelResult(String address, int channel) { }
- };
-
- private IBluetoothHeadsetCallback mHeadsetCallback = new IBluetoothHeadsetCallback.Stub() {
- public void onConnectHeadsetResult(String address, int resultCode) {
- if (resultCode == BluetoothHeadset.RESULT_SUCCESS) {
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
- } else {
- // Make toast in UI thread
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_FAILED_TO_CONNECT, resultCode,
- -1, address));
- }
- }
- };
-
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case HANDLE_CONNECTED:
- case HANDLE_DISCONNECTED:
- case HANDLE_CONNECTING:
- updateRemoteDeviceStatus((String) msg.obj);
- break;
- case HANDLE_FAILED_TO_CONNECT:
- updateRemoteDeviceStatus((String) msg.obj);
- String name = mBluetooth.getRemoteName((String) msg.obj);
- if (name == null) {
- name = (String) msg.obj;
- }
- if (msg.arg1 == BluetoothHeadset.RESULT_FAILURE) {
- Toast.makeText(BluetoothSettings.this,
- mRes.getString(R.string.failed_to_connect, name),
- Toast.LENGTH_SHORT).show();
- }
- break;
- case HANDLE_PIN_REQUEST:
- mPinDialog = showPinDialog(null, (String) msg.obj);
- break;
- case HANDLE_DISCOVERABLE_TIMEOUT:
- long nowTime = SystemClock.elapsedRealtime();
- int secondsLeft = mDiscoverableTime
- - (int) (nowTime - mDiscoverableStartTime) / 1000;
- if (secondsLeft > 0) {
- mBTVisibility.setSummaryOn(
- getResources().getString(R.string.bluetooth_is_discoverable,
- String.valueOf(secondsLeft)));
- sendMessageDelayed(obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
- } else {
- mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
- mBTVisibility.setChecked(false);
- }
- break;
- case HANDLE_INITIAL_SCAN:
- if (mBluetoothHeadset.getState() == BluetoothHeadset.STATE_ERROR &&
- ((Integer)msg.obj).intValue() < 2) {
- // Second attempt after another 100ms
- sendMessageDelayed(obtainMessage(HANDLE_INITIAL_SCAN, 2), 100);
- } else {
- resetDeviceListUI();
- if (mAutoDiscovery) {
- mBluetooth.cancelDiscovery();
- mBluetooth.startDiscovery();
- }
- }
- break;
- case HANDLE_PAIRING_PASSED:
- String addr = (String) msg.obj;
- pairingDone(addr, true);
- break;
- case HANDLE_PAIRING_FAILED:
- String address = (String) msg.obj;
- pairingDone(address, false);
- String pairName = mBluetooth.getRemoteName(address);
- if (pairName == null) {
- pairName = address;
- }
- Toast.makeText(BluetoothSettings.this,
- mRes.getString(R.string.failed_to_pair, pairName),
- Toast.LENGTH_SHORT).show();
- break;
- case HANDLE_PAUSE_TIMEOUT:
- // Possibility of race condition, but not really harmful
- if (!sIsRunning) {
- Object[] params = (Object[]) msg.obj;
- BluetoothDevice bluetooth = (BluetoothDevice) params[0];
- if (bluetooth.isEnabled()) {
- if (bluetooth.isDiscovering()) {
- bluetooth.cancelDiscovery();
- }
- if (params[1] != null) {
- bluetooth.cancelBondingProcess((String) params[1]);
- }
- bluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
- }
- }
- break;
- }
- }
- };
-
- private DialogInterface.OnClickListener mDisconnectListener =
- new DialogInterface.OnClickListener() {
-
- public void onClick(DialogInterface dialog, int which) {
- if (dialog == mPinDialog) {
- if (which == DialogInterface.BUTTON1) {
- String pin = mPinEdit.getText().toString();
- if (pin != null && pin.length() > 0) {
- sendPin(pin);
- } else {
- sendPin(null);
- }
- } else {
- sendPin(null);
- }
- mPinDialog = null;
- mPinEdit = null;
- } else {
- if (which == DialogInterface.BUTTON1) {
- disconnect();
- }
- }
- }
- };
-
- private DialogInterface.OnCancelListener mCancelListener =
- new DialogInterface.OnCancelListener() {
- public void onCancel(DialogInterface dialog) {
- if (dialog == mPinDialog) {
- sendPin(null);
- }
- mPinDialog = null;
- mPinEdit = null;
- }
- };
-}
-
diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java
index 973e655246..3097e96832 100644
--- a/src/com/android/settings/ChooseLockPattern.java
+++ b/src/com/android/settings/ChooseLockPattern.java
@@ -45,6 +45,17 @@ import java.util.List;
*/
public class ChooseLockPattern extends Activity implements View.OnClickListener{
+ /**
+ * Used by the choose lock pattern wizard to indicate the wizard is
+ * finished, and each activity in the wizard should finish.
+ * <p>
+ * Previously, each activity in the wizard would finish itself after
+ * starting the next activity. However, this leads to broken 'Back'
+ * behavior. So, now an activity does not finish itself until it gets this
+ * result.
+ */
+ static final int RESULT_FINISHED = RESULT_FIRST_USER;
+
// how long after a confirmation message is shown before moving on
static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
@@ -298,6 +309,8 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{
mLockPatternView.clearPattern();
updateStage(Stage.Introduction);
} else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
+ // They are canceling the entire wizard
+ setResult(RESULT_FINISHED);
finish();
} else {
throw new IllegalStateException("left footer button pressed, but stage of " +
@@ -368,6 +381,7 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{
}
if (resultCode != Activity.RESULT_OK) {
+ setResult(RESULT_FINISHED);
finish();
}
updateStage(Stage.Introduction);
@@ -475,6 +489,8 @@ public class ChooseLockPattern extends Activity implements View.OnClickListener{
mLockPatternUtils.setLockPatternEnabled(true);
mLockPatternUtils.setVisiblePatternEnabled(true);
}
+
+ setResult(RESULT_FINISHED);
finish();
}
}
diff --git a/src/com/android/settings/ChooseLockPatternExample.java b/src/com/android/settings/ChooseLockPatternExample.java
index 5feba4cbd3..77517b9d3d 100644
--- a/src/com/android/settings/ChooseLockPatternExample.java
+++ b/src/com/android/settings/ChooseLockPatternExample.java
@@ -25,6 +25,7 @@ import android.view.View;
import android.widget.ImageView;
public class ChooseLockPatternExample extends Activity implements View.OnClickListener {
+ private static final int REQUESTCODE_CHOOSE = 1;
private static final long START_DELAY = 1000;
protected static final String TAG = "Settings";
private View mNextButton;
@@ -59,15 +60,24 @@ public class ChooseLockPatternExample extends Activity implements View.OnClickLi
public void onClick(View v) {
if (v == mSkipButton) {
+ // Canceling, so finish all
+ setResult(ChooseLockPattern.RESULT_FINISHED);
finish();
} else if (v == mNextButton) {
stopAnimation(mAnimation);
Intent intent = new Intent(this, ChooseLockPattern.class);
- startActivity(intent);
+ startActivityForResult(intent, REQUESTCODE_CHOOSE);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUESTCODE_CHOOSE && resultCode == ChooseLockPattern.RESULT_FINISHED) {
+ setResult(resultCode);
finish();
}
}
-
+
private void initViews() {
mNextButton = findViewById(R.id.next_button);
mNextButton.setOnClickListener(this);
diff --git a/src/com/android/settings/ChooseLockPatternTutorial.java b/src/com/android/settings/ChooseLockPatternTutorial.java
index 9687b55f10..a0a878a055 100644
--- a/src/com/android/settings/ChooseLockPatternTutorial.java
+++ b/src/com/android/settings/ChooseLockPatternTutorial.java
@@ -24,8 +24,10 @@ import android.os.Bundle;
import android.view.View;
public class ChooseLockPatternTutorial extends Activity implements View.OnClickListener {
- protected View mNextButton;
- protected View mSkipButton;
+ private static final int REQUESTCODE_EXAMPLE = 1;
+
+ private View mNextButton;
+ private View mSkipButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -52,11 +54,22 @@ public class ChooseLockPatternTutorial extends Activity implements View.OnClickL
public void onClick(View v) {
if (v == mSkipButton) {
+ // Canceling, so finish all
+ setResult(ChooseLockPattern.RESULT_FINISHED);
finish();
} else if (v == mNextButton) {
- startActivity(new Intent(this, ChooseLockPatternExample.class));
+ startActivityForResult(new Intent(this, ChooseLockPatternExample.class),
+ REQUESTCODE_EXAMPLE);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUESTCODE_EXAMPLE && resultCode == ChooseLockPattern.RESULT_FINISHED) {
+ setResult(resultCode);
finish();
}
}
+
}
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
index ce9dd8cb54..ead38d1579 100644
--- a/src/com/android/settings/DateTimeSettings.java
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -27,7 +27,6 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.os.SystemClock;
-import android.pim.DateFormat;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
@@ -35,6 +34,7 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.text.format.DateFormat;
import android.widget.DatePicker;
import android.widget.TimePicker;
@@ -281,9 +281,7 @@ public class DateTimeSettings
/* Get & Set values from the system settings */
private boolean is24Hour() {
- String setting = Settings.System.getString(getContentResolver(),
- Settings.System.TIME_12_24);
- return HOURS_24.equals(setting);
+ return DateFormat.is24HourFormat(this);
}
private void set24Hour(boolean is24Hour) {
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 02b852b6a0..155f085f5f 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -16,6 +16,7 @@
package com.android.settings;
+import android.os.BatteryManager;
import android.os.Bundle;
import android.os.SystemProperties;
import android.preference.Preference;
@@ -32,9 +33,11 @@ public class DevelopmentSettings extends PreferenceActivity {
private static final String ENABLE_ADB = "enable_adb";
private static final String KEEP_SCREEN_ON = "keep_screen_on";
+ private static final String ALLOW_MOCK_LOCATION = "allow_mock_location";
private CheckBoxPreference mEnableAdb;
private CheckBoxPreference mKeepScreenOn;
+ private CheckBoxPreference mAllowMockLocation;
@Override
protected void onCreate(Bundle icicle) {
@@ -44,16 +47,19 @@ public class DevelopmentSettings extends PreferenceActivity {
mEnableAdb = (CheckBoxPreference) findPreference(ENABLE_ADB);
mKeepScreenOn = (CheckBoxPreference) findPreference(KEEP_SCREEN_ON);
+ mAllowMockLocation = (CheckBoxPreference) findPreference(ALLOW_MOCK_LOCATION);
}
@Override
protected void onResume() {
super.onResume();
- mEnableAdb.setChecked(Settings.System.getInt(getContentResolver(),
- Settings.System.ADB_ENABLED, 0) != 0);
+ mEnableAdb.setChecked(Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ADB_ENABLED, 0) != 0);
mKeepScreenOn.setChecked(Settings.System.getInt(getContentResolver(),
Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0) != 0);
+ mAllowMockLocation.setChecked(Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0);
}
@Override
@@ -66,11 +72,15 @@ public class DevelopmentSettings extends PreferenceActivity {
}
if (preference == mEnableAdb) {
- Settings.System.putInt(getContentResolver(), Settings.System.ADB_ENABLED,
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.ADB_ENABLED,
mEnableAdb.isChecked() ? 1 : 0);
} else if (preference == mKeepScreenOn) {
Settings.System.putInt(getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN,
- mKeepScreenOn.isChecked() ? 1 : 0);
+ mKeepScreenOn.isChecked() ?
+ (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB) : 0);
+ } else if (preference == mAllowMockLocation) {
+ Settings.Secure.putInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION,
+ mAllowMockLocation.isChecked() ? 1 : 0);
}
return false;
diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java
index 58fc91e989..5d72afcacd 100644
--- a/src/com/android/settings/DeviceInfoSettings.java
+++ b/src/com/android/settings/DeviceInfoSettings.java
@@ -46,6 +46,7 @@ public class DeviceInfoSettings extends PreferenceActivity {
private static final String KEY_TERMS = "terms";
private static final String KEY_LICENSE = "license";
private static final String KEY_COPYRIGHT = "copyright";
+ private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
@Override
protected void onCreate(Bundle icicle) {
@@ -56,7 +57,7 @@ public class DeviceInfoSettings extends PreferenceActivity {
setSummary("firmware_version", "ro.build.version.release");
setSummary("baseband_version", "gsm.version.baseband");
setSummary("device_model", "ro.product.model");
- setSummary("build_number", "ro.build.description");
+ setSummary("build_number", "ro.build.version.incremental");
findPreference("kernel_version").setSummary(getFormattedKernelVersion());
/*
@@ -74,6 +75,8 @@ public class DeviceInfoSettings extends PreferenceActivity {
Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_TEAM,
Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+ Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_SYSTEM_UPDATE_SETTINGS,
+ Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
}
private void setSummary(String preference, String property) {
diff --git a/src/com/android/settings/InputMethodsSettings.java b/src/com/android/settings/InputMethodsSettings.java
new file mode 100644
index 0000000000..d38779d0aa
--- /dev/null
+++ b/src/com/android/settings/InputMethodsSettings.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.settings;
+
+import java.util.HashSet;
+import java.util.List;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.preference.CheckBoxPreference;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+/*
+ * Displays preferences for input methods.
+ */
+public class InputMethodsSettings extends PreferenceActivity {
+ private List<InputMethodInfo> mInputMethodProperties;
+
+ final TextUtils.SimpleStringSplitter mStringColonSplitter
+ = new TextUtils.SimpleStringSplitter(':');
+
+ private String mLastInputMethodId;
+ private String mLastTickedInputMethodId;
+
+ static public String getInputMethodIdFromKey(String key) {
+ return key;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ addPreferencesFromResource(R.xml.input_methods_prefs);
+
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ mInputMethodProperties = imm.getInputMethodList();
+
+ mLastInputMethodId = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+
+ int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties
+ .size());
+ for (int i = 0; i < N; ++i) {
+ InputMethodInfo property = mInputMethodProperties.get(i);
+ String prefKey = property.getId();
+
+ CharSequence label = property.loadLabel(getPackageManager());
+
+ // Add a check box.
+ CheckBoxPreference chkbxPref = new CheckBoxPreference(this);
+ chkbxPref.setKey(prefKey);
+ chkbxPref.setTitle(label);
+ getPreferenceScreen().addPreference(chkbxPref);
+
+ // If setting activity is available, add a setting screen entry.
+ if (null != property.getSettingsActivity()) {
+ PreferenceScreen prefScreen = new PreferenceScreen(this, null);
+ prefScreen.setKey(property.getSettingsActivity());
+ // XXX TODO: handle localization properly.
+ prefScreen.setTitle(label + " settings");
+ getPreferenceScreen().addPreference(prefScreen);
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ final HashSet<String> enabled = new HashSet<String>();
+ String enabledStr = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS);
+ if (enabledStr != null) {
+ final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(enabledStr);
+ while (splitter.hasNext()) {
+ enabled.add(splitter.next());
+ }
+ }
+
+ // Update the statuses of the Check Boxes.
+ int N = mInputMethodProperties.size();
+ for (int i = 0; i < N; ++i) {
+ final String id = mInputMethodProperties.get(i).getId();
+ CheckBoxPreference pref = (CheckBoxPreference) findPreference(mInputMethodProperties
+ .get(i).getId());
+ pref.setChecked(enabled.contains(id));
+ }
+ mLastTickedInputMethodId = null;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ StringBuilder builder = new StringBuilder(256);
+
+ boolean haveLastInputMethod = false;
+
+ int firstEnabled = -1;
+ int N = mInputMethodProperties.size();
+ for (int i = 0; i < N; ++i) {
+ final String id = mInputMethodProperties.get(i).getId();
+ CheckBoxPreference pref = (CheckBoxPreference) findPreference(id);
+ boolean hasIt = id.equals(mLastInputMethodId);
+ if (pref.isChecked()) {
+ if (builder.length() > 0) builder.append(':');
+ builder.append(id);
+ if (firstEnabled < 0) {
+ firstEnabled = i;
+ }
+ if (hasIt) haveLastInputMethod = true;
+ } else if (hasIt) {
+ mLastInputMethodId = mLastTickedInputMethodId;
+ }
+ }
+
+ // If the last input method is unset, set it as the first enabled one.
+ if (null == mLastInputMethodId || "".equals(mLastInputMethodId)) {
+ if (firstEnabled >= 0) {
+ mLastInputMethodId = mInputMethodProperties.get(firstEnabled).getId();
+ } else {
+ mLastInputMethodId = null;
+ }
+ }
+
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD, mLastInputMethodId);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+
+ // Those monkeys kept committing suicide, so we add this property
+ // to disable this functionality
+ if (!TextUtils.isEmpty(SystemProperties.get("ro.monkey"))) {
+ return false;
+ }
+
+ if (preference instanceof CheckBoxPreference) {
+ CheckBoxPreference chkPref = (CheckBoxPreference) preference;
+ String id = getInputMethodIdFromKey(chkPref.getKey());
+ if (chkPref.isChecked()) {
+ mLastTickedInputMethodId = id;
+ } else if (id.equals(mLastTickedInputMethodId)) {
+ mLastTickedInputMethodId = null;
+ }
+ } else if (preference instanceof PreferenceScreen) {
+ if (preference.getIntent() == null) {
+ PreferenceScreen pref = (PreferenceScreen) preference;
+ String activityName = pref.getKey();
+ String packageName = activityName.substring(0, activityName
+ .lastIndexOf("."));
+ if (activityName.length() > 0) {
+ Intent i = new Intent(Intent.ACTION_MAIN);
+ i.setClassName(packageName, activityName);
+ startActivity(i);
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/com/android/settings/InstalledAppDetails.java b/src/com/android/settings/InstalledAppDetails.java
index 712f94db0e..d3e73449d5 100644
--- a/src/com/android/settings/InstalledAppDetails.java
+++ b/src/com/android/settings/InstalledAppDetails.java
@@ -29,7 +29,6 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageStatsObserver;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -37,6 +36,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.text.format.Formatter;
import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
@@ -50,33 +50,62 @@ import android.widget.LinearLayout;
import android.widget.TextView;
/**
- * Activity to display application information from Settings
- *
+ * Activity to display application information from Settings. This activity presents
+ * extended information associated with a package like code, data, total size, permissions
+ * used by the application and also the set of default launchable activities.
+ * For system applications, an option to clear user data is displayed only if data size is > 0.
+ * System applications that do not want clear user data do not have this option.
+ * For non-system applications, there is no option to clear data. Instead there is an option to
+ * uninstall the application.
*/
public class InstalledAppDetails extends Activity implements View.OnClickListener, DialogInterface.OnClickListener {
private static final String TAG="InstalledAppDetails";
private static final int _UNKNOWN_APP=R.string.unknown;
- //wait times used for the async package manager api
private ApplicationInfo mAppInfo;
- private Button mUninstallButton;
+ private Button mAppButton;
private Button mActivitiesButton;
- private boolean mSysPackage;
- private boolean localLOGV=Config.LOGV || true;
+ private boolean mCanUninstall;
+ private boolean localLOGV=Config.LOGV || false;
private TextView mTotalSize;
private TextView mAppSize;
private TextView mDataSize;
+ private PkgSizeObserver mSizeObserver;
+ private ClearUserDataObserver mClearDataObserver;
+ // Views related to cache info
+ private View mCachePanel;
+ private TextView mCacheSize;
+ private Button mClearCacheButton;
+ private ClearCacheObserver mClearCacheObserver;
+
PackageStats mSizeInfo;
private Button mManageSpaceButton;
private PackageManager mPm;
- private String mBStr, mKbStr, mMbStr;
//internal constants used in Handler
- private static final int CLEAR_USER_DATA = 1;
private static final int OP_SUCCESSFUL = 1;
private static final int OP_FAILED = 2;
+ private static final int CLEAR_USER_DATA = 1;
private static final int GET_PKG_SIZE = 2;
+ private static final int CLEAR_CACHE = 3;
private static final String ATTR_PACKAGE_STATS="PackageStats";
+ // invalid size value used initially and also when size retrieval through PackageManager
+ // fails for whatever reason
+ private static final int SIZE_INVALID = -1;
+
+ // Resource strings
+ private CharSequence mInvalidSizeStr;
+ private CharSequence mComputingStr;
+ private CharSequence mAppButtonText;
+
+ // Possible btn states
+ private enum AppButtonStates {
+ CLEAR_DATA,
+ UNINSTALL,
+ NONE
+ }
+ private AppButtonStates mAppButtonState;
+
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -86,17 +115,22 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
case GET_PKG_SIZE:
refreshSizeInfo(msg);
break;
+ case CLEAR_CACHE:
+ // Refresh size info
+ mPm.getPackageSizeInfo(mAppInfo.packageName, mSizeObserver);
+ break;
default:
break;
}
}
};
- private boolean isSystemPackage() {
- if ((mAppInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
+ private boolean isUninstallable() {
+ if (((mAppInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) &&
+ ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0)) {
+ return false;
}
- return false;
+ return true;
}
class ClearUserDataObserver extends IPackageDataObserver.Stub {
@@ -119,28 +153,46 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
}
}
+ class ClearCacheObserver extends IPackageDataObserver.Stub {
+ public void onRemoveCompleted(final String packageName, final boolean succeeded) {
+ final Message msg = mHandler.obtainMessage(CLEAR_CACHE);
+ msg.arg1 = succeeded?OP_SUCCESSFUL:OP_FAILED;
+ mHandler.sendMessage(msg);
+ }
+ }
+
private String getSizeStr(long size) {
- String retStr = "";
- if(size < 1024) {
- return String.valueOf(size)+mBStr;
+ if (size == SIZE_INVALID) {
+ return mInvalidSizeStr.toString();
}
- long kb, mb, rem;
- kb = size >> 10;
- rem = size - (kb << 10);
- if(kb < 1024) {
- if(rem > 512) {
- kb++;
+ return Formatter.formatFileSize(this, size);
+ }
+
+ private void setAppBtnState() {
+ boolean visible = false;
+ if(mCanUninstall) {
+ //app can clear user data
+ if((mAppInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)
+ == ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) {
+ mAppButtonText = getText(R.string.clear_user_data_text);
+ mAppButtonState = AppButtonStates.CLEAR_DATA;
+ visible = true;
+ } else {
+ //hide button if diableClearUserData is set
+ visible = false;
+ mAppButtonState = AppButtonStates.NONE;
}
- retStr += String.valueOf(kb)+mKbStr;
- return retStr;
+ } else {
+ visible = true;
+ mAppButtonState = AppButtonStates.UNINSTALL;
+ mAppButtonText = getText(R.string.uninstall_text);
+ }
+ if(visible) {
+ mAppButton.setText(mAppButtonText);
+ mAppButton.setVisibility(View.VISIBLE);
+ } else {
+ mAppButton.setVisibility(View.GONE);
}
- mb = kb >> 10;
- if(kb >= 512) {
- //round off
- mb++;
- }
- retStr += String.valueOf(mb)+ mMbStr;
- return retStr;
}
/** Called when the activity is first created. */
@@ -152,45 +204,25 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
//get application's name from intent
Intent intent = getIntent();
final String packageName = intent.getStringExtra(ManageApplications.APP_PKG_NAME);
- mSizeInfo = intent.getParcelableExtra(ManageApplications.APP_PKG_SIZE);
- long total = -1;
- long code = -1;
- long data = -1;
- if(mSizeInfo != null) {
- total = mSizeInfo.cacheSize+mSizeInfo.codeSize+mSizeInfo.dataSize;
- code = mSizeInfo.codeSize;
- data = mSizeInfo.dataSize+mSizeInfo.cacheSize;
- }
- String unknownStr = getString(_UNKNOWN_APP);
- mBStr = getString(R.string.b_text);
- mKbStr = getString(R.string.kb_text);
- mMbStr = getString(R.string.mb_text);
- String totalSizeStr = unknownStr;
- if(total != -1) {
- totalSizeStr = getSizeStr(total);
- }
- String appSizeStr = unknownStr;
- if(code != -1) {
- appSizeStr = getSizeStr(code);
- }
- String dataSizeStr = unknownStr;
- if(data != -1) {
- dataSizeStr = getSizeStr(data);
- }
- if(localLOGV) Log.i(TAG, "packageName:"+packageName+", total="+total+
- "code="+code+", data="+data);
+ mComputingStr = getText(R.string.computing_size);
+ // Try retrieving package stats again
+ CharSequence totalSizeStr, appSizeStr, dataSizeStr;
+ totalSizeStr = appSizeStr = dataSizeStr = mComputingStr;
+ if(localLOGV) Log.i(TAG, "Have to compute package sizes");
+ mSizeObserver = new PkgSizeObserver();
+ mPm.getPackageSizeInfo(packageName, mSizeObserver);
+
try {
- mAppInfo = mPm.getApplicationInfo(packageName, 0);
+ mAppInfo = mPm.getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
- Throwable th = e.fillInStackTrace();
Log.e(TAG, "Exception when retrieving package:"+packageName, e);
displayErrorDialog(R.string.app_not_found_dlg_text, true, true);
}
- setContentView(R.layout.installed_app_details);
- ((ImageView)findViewById(R.id.app_icon)).setImageDrawable(mPm.
- getApplicationIcon(mAppInfo));
+ setContentView(R.layout.installed_app_details);
+ ((ImageView)findViewById(R.id.app_icon)).setImageDrawable(mAppInfo.loadIcon(mPm));
//set application name TODO version
- CharSequence appName = mPm.getApplicationLabel(mAppInfo);
+ CharSequence appName = mAppInfo.loadLabel(mPm);
if(appName == null) {
appName = getString(_UNKNOWN_APP);
}
@@ -208,33 +240,22 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
mDataSize = (TextView)findViewById(R.id.data_size_text);
mDataSize.setText(dataSizeStr);
- mUninstallButton = ((Button)findViewById(R.id.uninstall_button));
+ mAppButton = ((Button)findViewById(R.id.uninstall_button));
//determine if app is a system app
- mSysPackage = isSystemPackage();
- if(localLOGV) Log.i(TAG, "Is systemPackage "+mSysPackage);
- int btnText;
- boolean btnClickable = true;
-
- if(mSysPackage) {
- //app can clear user data
- if((mAppInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)
- == ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) {
- mUninstallButton.setText(R.string.clear_user_data_text);
- //disable button if data is 0
- if(data == 0) {
- mUninstallButton.setEnabled(false);
- } else {
- //enable button
- mUninstallButton.setOnClickListener(this);
- }
- } else {
- //hide button if diableClearUserData is set
- mUninstallButton.setVisibility(View.GONE);
- }
- } else {
- mUninstallButton.setText(R.string.uninstall_text);
- mUninstallButton.setOnClickListener(this);
+ mCanUninstall = !isUninstallable();
+ if(localLOGV) Log.i(TAG, "Is systemPackage "+mCanUninstall);
+ setAppBtnState();
+ mManageSpaceButton = (Button)findViewById(R.id.manage_space_button);
+ if(mAppInfo.manageSpaceActivityName != null) {
+ mManageSpaceButton.setVisibility(View.VISIBLE);
+ mManageSpaceButton.setOnClickListener(this);
}
+
+ // Cache section
+ mCachePanel = findViewById(R.id.cache_panel);
+ mCacheSize = (TextView) findViewById(R.id.cache_size_text);
+ mClearCacheButton = (Button) findViewById(R.id.clear_cache_button);
+
//clear activities
mActivitiesButton = (Button)findViewById(R.id.clear_activities_button);
List<ComponentName> prefActList = new ArrayList<ComponentName>();
@@ -251,23 +272,19 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
autoLaunchView.setText(R.string.auto_launch_enable_text);
mActivitiesButton.setOnClickListener(this);
}
- mManageSpaceButton = (Button)findViewById(R.id.manage_space_button);
- if(mAppInfo.manageSpaceActivityName != null) {
- mManageSpaceButton.setVisibility(View.VISIBLE);
- mManageSpaceButton.setOnClickListener(this);
+
+ // security permissions section
+ LinearLayout permsView = (LinearLayout) findViewById(R.id.permissions_section);
+ AppSecurityPermissions asp = new AppSecurityPermissions(this, packageName);
+ if(asp.getPermissionCount() > 0) {
+ permsView.setVisibility(View.VISIBLE);
+ // Make the security sections header visible
+ LinearLayout securityList = (LinearLayout) permsView.findViewById(
+ R.id.security_settings_list);
+ securityList.addView(asp.getPermissionsView());
+ } else {
+ permsView.setVisibility(View.GONE);
}
- //security permissions section
- AppSecurityPermissions asp = new AppSecurityPermissions(this);
- PackageInfo pkgInfo;
- try {
- pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Couldnt retrieve permissions for package:"+packageName);
- return;
- }
- asp.setSecurityPermissionsView(pkgInfo);
- LinearLayout securityList = (LinearLayout) findViewById(R.id.security_settings_list);
- securityList.addView(asp.getPermissionsView());
}
private void displayErrorDialog(int msgId, final boolean finish, final boolean changed) {
@@ -292,7 +309,7 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
Intent intent = new Intent();
intent.putExtra(ManageApplications.APP_CHG, appChanged);
setResult(ManageApplications.RESULT_OK, intent);
- mUninstallButton.setEnabled(false);
+ mAppButton.setEnabled(false);
if(finish) {
finish();
}
@@ -305,29 +322,53 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
*/
private void refreshSizeInfo(Message msg) {
boolean changed = false;
- Intent intent = new Intent();
PackageStats newPs = msg.getData().getParcelable(ATTR_PACKAGE_STATS);
long newTot = newPs.cacheSize+newPs.codeSize+newPs.dataSize;
- long oldTot = mSizeInfo.cacheSize+mSizeInfo.codeSize+mSizeInfo.dataSize;
- if(newTot != oldTot) {
+ if(mSizeInfo == null) {
+ mSizeInfo = newPs;
mTotalSize.setText(getSizeStr(newTot));
- changed = true;
- }
- if(newPs.codeSize != mSizeInfo.codeSize) {
mAppSize.setText(getSizeStr(newPs.codeSize));
- changed = true;
- }
- if((newPs.dataSize != mSizeInfo.dataSize) || (newPs.cacheSize != mSizeInfo.cacheSize)) {
mDataSize.setText(getSizeStr(newPs.dataSize+newPs.cacheSize));
- changed = true;
+ } else {
+ long oldTot = mSizeInfo.cacheSize+mSizeInfo.codeSize+mSizeInfo.dataSize;
+ if(newTot != oldTot) {
+ mTotalSize.setText(getSizeStr(newTot));
+ changed = true;
+ }
+ if(newPs.codeSize != mSizeInfo.codeSize) {
+ mAppSize.setText(getSizeStr(newPs.codeSize));
+ changed = true;
+ }
+ if((newPs.dataSize != mSizeInfo.dataSize) || (newPs.cacheSize != mSizeInfo.cacheSize)) {
+ mDataSize.setText(getSizeStr(newPs.dataSize+newPs.cacheSize));
+ }
+ if(changed) {
+ mSizeInfo = newPs;
+ }
}
- if(changed) {
- mUninstallButton.setText(R.string.clear_user_data_text);
- mSizeInfo = newPs;
- intent.putExtra(ManageApplications.APP_PKG_SIZE, mSizeInfo);
+
+ long data = mSizeInfo.dataSize+mSizeInfo.cacheSize;
+ // Disable button if data is 0
+ if(mAppButtonState != AppButtonStates.NONE){
+ mAppButton.setText(mAppButtonText);
+ if((mAppButtonState == AppButtonStates.CLEAR_DATA) && (data == 0)) {
+ mAppButton.setEnabled(false);
+ } else {
+ mAppButton.setEnabled(true);
+ mAppButton.setOnClickListener(this);
+ }
+ }
+ refreshCacheInfo(newPs.cacheSize);
+ }
+
+ private void refreshCacheInfo(long cacheSize) {
+ // Set cache info
+ mCacheSize.setText(getSizeStr(cacheSize));
+ if (cacheSize <= 0) {
+ mClearCacheButton.setEnabled(false);
+ } else {
+ mClearCacheButton.setOnClickListener(this);
}
- intent.putExtra(ManageApplications.APP_CHG, changed);
- setResult(ManageApplications.RESULT_OK, intent);
}
/*
@@ -339,11 +380,10 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
String packageName = mAppInfo.packageName;
if(result == OP_SUCCESSFUL) {
Log.i(TAG, "Cleared user data for system package:"+packageName);
- PkgSizeObserver observer = new PkgSizeObserver();
- mPm.getPackageSizeInfo(packageName, observer);
+ mPm.getPackageSizeInfo(packageName, mSizeObserver);
} else {
- mUninstallButton.setText(R.string.clear_user_data_text);
- mUninstallButton.setEnabled(true);
+ mAppButton.setText(R.string.clear_user_data_text);
+ mAppButton.setEnabled(true);
}
}
@@ -352,20 +392,21 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
* button for a system package
*/
private void initiateClearUserDataForSysPkg() {
- mUninstallButton.setEnabled(false);
+ mAppButton.setEnabled(false);
//invoke uninstall or clear user data based on sysPackage
- boolean recomputeSizes = false;
String packageName = mAppInfo.packageName;
Log.i(TAG, "Clearing user data for system package");
- ClearUserDataObserver observer = new ClearUserDataObserver();
+ if(mClearDataObserver == null) {
+ mClearDataObserver = new ClearUserDataObserver();
+ }
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
- boolean res = am.clearApplicationUserData(packageName, observer);
+ boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
if(!res) {
//doesnt initiate clear. some error. should not happen but just log error for now
Log.i(TAG, "Couldnt clear application user data for package:"+packageName);
displayErrorDialog(R.string.clear_data_failed, false, false);
} else {
- mUninstallButton.setText(R.string.recompute_size);
+ mAppButton.setText(R.string.recompute_size);
}
}
@@ -375,8 +416,8 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
*/
public void onClick(View v) {
String packageName = mAppInfo.packageName;
- if(v == mUninstallButton) {
- if(mSysPackage) {
+ if(v == mAppButton) {
+ if(mCanUninstall) {
//display confirmation dialog
new AlertDialog.Builder(this)
.setTitle(getString(R.string.clear_data_dlg_title))
@@ -399,11 +440,17 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
Intent intent = new Intent(Intent.ACTION_DEFAULT);
intent.setClassName(mAppInfo.packageName, mAppInfo.manageSpaceActivityName);
startActivityForResult(intent, -1);
+ } else if (v == mClearCacheButton) {
+ // Lazy initialization of observer
+ if (mClearCacheObserver == null) {
+ mClearCacheObserver = new ClearCacheObserver();
+ }
+ mPm.deleteApplicationCacheFiles(packageName, mClearCacheObserver);
}
}
public void onClick(DialogInterface dialog, int which) {
- if(which == AlertDialog.BUTTON1) {
+ if(which == AlertDialog.BUTTON_POSITIVE) {
//invoke uninstall or clear user data based on sysPackage
initiateClearUserDataForSysPkg();
} else {
diff --git a/src/com/android/settings/LocalePicker.java b/src/com/android/settings/LocalePicker.java
index 46d9b52b68..9ee82601be 100644
--- a/src/com/android/settings/LocalePicker.java
+++ b/src/com/android/settings/LocalePicker.java
@@ -63,30 +63,55 @@ public class LocalePicker extends ListActivity {
setContentView(getContentView());
String[] locales = getAssets().getLocales();
- final int N = locales.length;
- mLocales = new Loc[N];
- for (int i = 0; i < N; i++) {
- Locale locale = null;
+ Arrays.sort(locales);
+
+ final int origSize = locales.length;
+ Loc[] preprocess = new Loc[origSize];
+ int finalSize = 0;
+ for (int i = 0 ; i < origSize; i++ ) {
String s = locales[i];
int len = s.length();
- if (len == 0) {
- locale = new Locale("en", "US");
- } else if (len == 2) {
- locale = new Locale(s);
+ if (len == 2) {
+ Locale l = new Locale(s);
+ preprocess[finalSize++] = new Loc(l.getDisplayLanguage(), l);
} else if (len == 5) {
- locale = new Locale(s.substring(0, 2), s.substring(3, 5));
- }
- String displayName = "";
- if (locale != null) {
- displayName = locale.getDisplayName();
+ String language = s.substring(0, 2);
+ String country = s.substring(3, 5);
+ Locale l = new Locale(language, country);
+
+ if (finalSize == 0) {
+ preprocess[finalSize++] = new Loc(l.getDisplayLanguage(), l);
+ } else {
+ // check previous entry:
+ // same lang and no country -> overwrite it with a lang-only name
+ // same lang and a country -> upgrade to full name and
+ // insert ours with full name
+ // diff lang -> insert ours with lang-only name
+ if (preprocess[finalSize-1].locale.getLanguage().equals(language)) {
+ String prevCountry = preprocess[finalSize-1].locale.getCountry();
+ if (prevCountry.length() == 0) {
+ preprocess[finalSize-1].locale = l;
+ preprocess[finalSize-1].label = l.getDisplayLanguage();
+ } else {
+ preprocess[finalSize-1].label = preprocess[finalSize-1].locale.getDisplayName();
+ preprocess[finalSize++] = new Loc(l.getDisplayName(), l);
+ }
+ } else {
+ String displayName;
+ if (s.equals("zz_ZZ")) {
+ displayName = "Pseudo...";
+ } else {
+ displayName = l.getDisplayLanguage();
+ }
+ preprocess[finalSize++] = new Loc(displayName, l);
+ }
+ }
}
- if ("zz_ZZ".equals(s)) {
- displayName = "Pseudo...";
- }
-
- mLocales[i] = new Loc(displayName, locale);
}
-
+ mLocales = new Loc[finalSize];
+ for (int i = 0; i < finalSize ; i++) {
+ mLocales[i] = preprocess[i];
+ }
int layoutId = R.layout.locale_picker_item;
int fieldId = R.id.locale;
ArrayAdapter<Loc> adapter = new ArrayAdapter<Loc>(this, layoutId, fieldId, mLocales);
@@ -107,25 +132,11 @@ public class LocalePicker extends ListActivity {
Loc loc = mLocales[position];
config.locale = loc.locale;
- final String language = loc.locale.getLanguage();
- final String region = loc.locale.getCountry();
+
+ // indicate this isn't some passing default - the user wants this remembered
+ config.userSetLocale = true;
am.updateConfiguration(config);
-
- // Update the System properties
- SystemProperties.set("user.language", language);
- SystemProperties.set("user.region", region);
- // Write to file for persistence across reboots
- try {
- BufferedWriter bw = new BufferedWriter(new java.io.FileWriter(
- System.getenv("ANDROID_DATA") + "/locale"));
- bw.write(language + "_" + region);
- bw.close();
- } catch (java.io.IOException ioe) {
- Log.e(TAG,
- "Unable to persist locale. Error writing to locale file."
- + ioe);
- }
} catch (RemoteException e) {
// Intentionally left blank
}
diff --git a/src/com/android/settings/ManageApplications.java b/src/com/android/settings/ManageApplications.java
index 8389502518..f1550f9780 100644
--- a/src/com/android/settings/ManageApplications.java
+++ b/src/com/android/settings/ManageApplications.java
@@ -18,8 +18,12 @@ package com.android.settings;
import com.android.settings.R;
import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ListActivity;
+import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
@@ -27,320 +31,954 @@ 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.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.text.format.Formatter;
import android.util.Config;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
import android.widget.AdapterView;
+import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
-import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
/**
* Activity to pick an application that will be used to display installation information and
- * options to upgrade/uninstall/delete user data for system applications.
+ * options to uninstall/delete user data for system applications. This activity
+ * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
+ * intent.
* Initially a compute in progress message is displayed while the application retrieves
- * the size information of installed packages which is done asynchronously through a
- * handler. Once the computation is done package resource information is retrieved
- * and then the information is displayed on the screen. All
- * messages are passed through a Handler object.
- * Known issue: There could be some ordering issues when installing/uninstalling
- * applications when the application list is being scanned.
+ * the list of application information from the PackageManager. The size information
+ * for each package is refreshed to the screen. The resource(app description and
+ * icon) information for each package is not available yet, so some default values for size
+ * icon and descriptions are used initially. Later the resource information for each
+ * application is retrieved and dynamically updated on the screen.
+ * A Broadcast receiver registers for package additions or deletions when the activity is
+ * in focus. If the user installs or deletes packages when the activity has focus, the receiver
+ * gets notified and proceeds to add/delete these packages from the list on the screen.
+ * This is an unlikely scenario but could happen. The entire list gets created every time
+ * the activity's onStart gets invoked. This is to avoid having the receiver for the entire
+ * life cycle of the application.
+ * The applications can be sorted either alphabetically or
+ * based on size(descending). If this activity gets launched under low memory
+ * situations(A low memory notification dispatches intent
+ * ACTION_MANAGE_PACKAGE_STORAGE) the list is sorted per size.
+ * If the user selects an application, extended info(like size, uninstall/clear data options,
+ * permissions info etc.,) is displayed via the InstalledAppDetails activity.
+ * This activity passes the package name and size information to the
+ * InstalledAppDetailsActivity to avoid recomputation of the package size information.
*/
-public class ManageApplications extends Activity implements SimpleAdapter.ViewBinder, OnItemClickListener {
+public class ManageApplications extends ListActivity implements
+ OnItemClickListener, DialogInterface.OnCancelListener {
+ // TAG for this activity
private static final String TAG = "ManageApplications";
- //Application prefix information
- public static final String APP_PKG_PREFIX="com.android.settings.";
- public static final String APP_PKG_NAME=APP_PKG_PREFIX+"ApplicationPkgName";
- public static final String APP_PKG_SIZE= APP_PKG_PREFIX+"size";
- public static final String APP_CHG=APP_PKG_PREFIX+"changed";
- //constant value that can be used to check return code from sub activity.
- private static final int INSTALLED_APP_DETAILS = 1;
- //application attributes passed to sub activity that displays more app info
- private static final String KEY_APP_NAME = "ApplicationName";
- private static final String KEY_APP_ICON = "ApplicationIcon";
- private static final String KEY_APP_DESC = "ApplicationDescription";
- private static final String KEY_APP_SIZE= "ApplicationSize";
- //sort order that can be changed through the menu
- public static final int SORT_ORDER_ALPHA = 0;
- public static final int SORT_ORDER_SIZE = 1;
- //key and resource values used in constructing map for SimpleAdapter
- private static final String sKeys[] = new String[] { KEY_APP_NAME, KEY_APP_ICON,
- KEY_APP_DESC, KEY_APP_SIZE};
- private static final int sResourceIds[] = new int[] { R.id.app_name, R.id.app_icon,
- R.id.app_description, R.id.app_size};
- //List of ApplicationInfo objects for various applications
- private List<ApplicationInfo> mAppList;
- //SimpleAdapter used for managing items in the list
- private SimpleAdapter mAppAdapter;
- //map used to store size information which is used for displaying size information
- //in this activity as well as the subactivity. this is to avoid invoking package manager
- //api to retrieve size information
- private HashMap<String, PackageStats> mSizeMap;
- private HashMap<String, Map<String, ?> > mAppAdapterMap;
- //sort order
- private int mSortOrder = SORT_ORDER_ALPHA;
- //log information boolean
+ // log information boolean
private boolean localLOGV = Config.LOGV || false;
- private ApplicationInfo mCurrentPkg;
- private int mCurrentPkgIdx = 0;
- private static final int COMPUTE_PKG_SIZE_START = 1;
- private static final int COMPUTE_PKG_SIZE_DONE = 2;
- private static final int REMOVE_PKG=3;
- private static final int REORDER_LIST=4;
- private static final int ADD_PKG=5;
- private static final String ATTR_APP_IDX="ApplicationIndex";
- private static final String ATTR_CHAINED="Chained";
+
+ // attributes used as keys when passing values to InstalledAppDetails activity
+ public static final String APP_PKG_PREFIX = "com.android.settings.";
+ public static final String APP_PKG_NAME = APP_PKG_PREFIX+"ApplicationPkgName";
+ public static final String APP_PKG_SIZE = APP_PKG_PREFIX+"size";
+ public static final String APP_CHG = APP_PKG_PREFIX+"changed";
+
+ // attribute name used in receiver for tagging names of added/deleted packages
private static final String ATTR_PKG_NAME="PackageName";
+ private static final String ATTR_APP_PKG_STATS="ApplicationPackageStats";
+
+ // constant value that can be used to check return code from sub activity.
+ private static final int INSTALLED_APP_DETAILS = 1;
+
+ // sort order that can be changed through the menu can be sorted alphabetically
+ // or size(descending)
+ private static final int MENU_OPTIONS_BASE = 0;
+ public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 0;
+ public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 1;
+ // Filter options used for displayed list of applications
+ public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 2;
+ public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 3;
+ public static final int FILTER_APPS_RUNNING = MENU_OPTIONS_BASE + 4;
+ // sort order
+ private int mSortOrder = SORT_ORDER_ALPHA;
+ // Filter value
+ int mFilterApps = FILTER_APPS_ALL;
+
+ // Custom Adapter used for managing items in the list
+ private AppInfoAdapter mAppInfoAdapter;
+
+ // messages posted to the handler
+ private static final int HANDLER_MESSAGE_BASE = 0;
+ private static final int COMPUTE_PKG_SIZE_START = HANDLER_MESSAGE_BASE+1;
+ private static final int COMPUTE_PKG_SIZE_DONE = HANDLER_MESSAGE_BASE+2;
+ private static final int REMOVE_PKG = HANDLER_MESSAGE_BASE+3;
+ private static final int REORDER_LIST = HANDLER_MESSAGE_BASE+4;
+ private static final int ADD_PKG_START = HANDLER_MESSAGE_BASE+5;
+ private static final int ADD_PKG_DONE = HANDLER_MESSAGE_BASE+6;
+ private static final int REFRESH_ICONS = HANDLER_MESSAGE_BASE+7;
+
+ // observer object used for computing pkg sizes
private PkgSizeObserver mObserver;
+ // local handle to PackageManager
private PackageManager mPm;
+ // Broadcast Receiver object that receives notifications for added/deleted
+ // packages
private PackageIntentReceiver mReceiver;
+ // atomic variable used to track if computing pkg sizes is in progress. should be volatile?
+
private boolean mDoneIniting = false;
- private String mKbStr;
- private String mMbStr;
- private String mBStr;
+ // default icon thats used when displaying applications initially before resource info is
+ // retrieved
+ private Drawable mDefaultAppIcon;
+
+ // temporary dialog displayed while the application info loads
+ private ProgressDialog mLoadingDlg = null;
+
+ // compute index used to track the application size computations
+ private int mComputeIndex;
+
+ // Size resource used for packages whose size computation failed for some reason
+ private CharSequence mInvalidSizeStr;
+ private CharSequence mComputingSizeStr;
+
+ // map used to store list of added and removed packages. Immutable Boolean
+ // variables indicate if a package has been added or removed. If a package is
+ // added or deleted multiple times a single entry with the latest operation will
+ // be recorded in the map.
+ private Map<String, Boolean> mAddRemoveMap;
+
+ // layout inflater object used to inflate views
+ private LayoutInflater mInflater;
+
+ // invalid size value used initially and also when size retrieval through PackageManager
+ // fails for whatever reason
+ private static final int SIZE_INVALID = -1;
+
+ // debug boolean variable to test delays from PackageManager API's
+ private boolean DEBUG_PKG_DELAY = false;
+
+ // Thread to load resources
+ ResourceLoaderThread mResourceThread;
+
+ String mCurrentPkgName;
+
+ //TODO implement a cache system
+ private Map<String, AppInfo> mAppPropCache;
/*
* Handler class to handle messages for various operations
+ * Most of the operations that effect Application related data
+ * are posted as messages to the handler to avoid synchronization
+ * when accessing these structures.
+ * When the size retrieval gets kicked off for the first time, a COMPUTE_PKG_SIZE_START
+ * message is posted to the handler which invokes the getSizeInfo for the pkg at index 0
+ * When the PackageManager's asynchronous call back through
+ * PkgSizeObserver.onGetStatsCompleted gets invoked, the application resources like
+ * label, description, icon etc., is loaded in the same thread and these values are
+ * set on the observer. The observer then posts a COMPUTE_PKG_SIZE_DONE message
+ * to the handler. This information is updated on the AppInfoAdapter associated with
+ * the list view of this activity and size info retrieval is initiated for the next package as
+ * indicated by mComputeIndex
+ * When a package gets added while the activity has focus, the PkgSizeObserver posts
+ * ADD_PKG_START message to the handler. If the computation is not in progress, the size
+ * is retrieved for the newly added package through the observer object and the newly
+ * installed app info is updated on the screen. If the computation is still in progress
+ * the package is added to an internal structure and action deferred till the computation
+ * is done for all the packages.
+ * When a package gets deleted, REMOVE_PKG is posted to the handler
+ * if computation is not in progress(as indicated by
+ * mDoneIniting), the package is deleted from the displayed list of apps. If computation is
+ * still in progress the package is added to an internal structure and action deferred till
+ * the computation is done for all packages.
+ * When the sizes of all packages is computed, the newly
+ * added or removed packages are processed in order.
+ * If the user changes the order in which these applications are viewed by hitting the
+ * menu key, REORDER_LIST message is posted to the handler. this sorts the list
+ * of items based on the sort order.
*/
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
PackageStats ps;
ApplicationInfo info;
Bundle data;
- String pkgName;
- int idx;
- int size;
- boolean chained = false;
+ String pkgName = null;
+ AppInfo appInfo;
data = msg.getData();
+ if(data != null) {
+ pkgName = data.getString(ATTR_PKG_NAME);
+ }
switch (msg.what) {
case COMPUTE_PKG_SIZE_START:
- mDoneIniting = false;
- //initialize lists
- mAppList = new ArrayList<ApplicationInfo>();
- mSizeMap = new HashMap<String, PackageStats>();
- mAppAdapterMap = new HashMap<String, Map<String, ?> >();
- //update application list from PackageManager
- mAppList = mPm.getInstalledApplications(0);
- if(mAppList.size() == 0) {
- return;
- }
- mCurrentPkgIdx = 0;
- mCurrentPkg = mAppList.get(0);
- if(localLOGV) Log.i(TAG, "Initiating compute sizes for first time");
- //register receiver
- mReceiver = new PackageIntentReceiver();
- mReceiver.registerReceiver();
- pkgName = mCurrentPkg.packageName;
- mObserver = new PkgSizeObserver(0);
- mObserver.invokeGetSizeInfo(pkgName, true);
+ if(localLOGV) Log.i(TAG, "Message COMPUTE_PKG_SIZE_START");
+ setProgressBarIndeterminateVisibility(true);
+ mComputeIndex = 0;
+ initAppList(mFilterApps);
break;
case COMPUTE_PKG_SIZE_DONE:
- ps = mObserver.ps;
- info = mObserver.appInfo;
- chained = data.getBoolean(ATTR_CHAINED);
- if(!mObserver.succeeded) {
- if(chained) {
- removePackageFromAppList(ps.packageName);
- } else {
- //do not go to adding phase
+ if(localLOGV) Log.i(TAG, "Message COMPUTE_PKG_SIZE_DONE");
+ if(pkgName == null) {
+ Log.w(TAG, "Ignoring message");
+ break;
+ }
+ ps = data.getParcelable(ATTR_APP_PKG_STATS);
+ if(ps == null) {
+ Log.i(TAG, "Invalid package stats for package:"+pkgName);
+ } else {
+ int pkgId = mAppInfoAdapter.getIndex(pkgName);
+ if(mComputeIndex != pkgId) {
+ //spurious call from stale observer
+ Log.w(TAG, "Stale call back from PkgSizeObserver");
break;
}
- } else {
- //insert size value
- mSizeMap.put(ps.packageName, ps);
- Map<String, Object> entry = createMapEntry(mPm.getApplicationLabel(info),
- mPm.getApplicationIcon(info),
- info.loadDescription(mPm),
- getSizeStr(ps));
- mAppAdapterMap.put(ps.packageName, entry);
+ mAppInfoAdapter.updateAppSize(pkgName, ps);
}
- if(chained) {
- //here app list is precomputed
- idx = data.getInt(ATTR_APP_IDX);
- //increment only if succeded
- if(mObserver.succeeded) {
- idx++;
+ mComputeIndex++;
+ if (mComputeIndex < mAppInfoAdapter.getCount()) {
+ // initiate compute package size for next pkg in list
+ mObserver.invokeGetSizeInfo(mAppInfoAdapter.getApplicationInfo(
+ mComputeIndex),
+ COMPUTE_PKG_SIZE_DONE);
+ } else {
+ // check for added/removed packages
+ Set<String> keys = mAddRemoveMap.keySet();
+ Iterator<String> iter = keys.iterator();
+ List<String> removeList = new ArrayList<String>();
+ boolean added = false;
+ boolean removed = false;
+ while (iter.hasNext()) {
+ String key = iter.next();
+ if (mAddRemoveMap.get(key) == Boolean.TRUE) {
+ // add
+ try {
+ info = mPm.getApplicationInfo(key, 0);
+ mAppInfoAdapter.addApplicationInfo(info);
+ added = true;
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Invalid added package:"+key+" Ignoring entry");
+ }
+ } else {
+ // remove
+ removeList.add(key);
+ removed = true;
+ }
}
- if(idx < mAppList.size()) {
- pkgName = mAppList.get(idx).packageName;
- //increment record index and invoke getSizeInfo for next record
- mObserver.invokeGetSizeInfo(pkgName, true);
+ // remove uninstalled packages from list
+ if (removed) {
+ mAppInfoAdapter.removeFromList(removeList);
+ }
+ // handle newly installed packages
+ if (added) {
+ mObserver.invokeGetSizeInfo(mAppInfoAdapter.getApplicationInfo(
+ mComputeIndex),
+ COMPUTE_PKG_SIZE_DONE);
} else {
- sortAppList();
- createListFromValues();
+ // end computation here
mDoneIniting = true;
- }
- } else {
- //add app info object as well
- mAppList.add(info);
- sortAppList();
- size = mAppList.size();
- int i;
- for(i = 0; i < size; i++) {
- if(mAppList.get(i).packageName.equalsIgnoreCase(mCurrentPkg.packageName)) {
- if(i > mCurrentPkgIdx) {
- mCurrentPkgIdx = i;
- }
- break;
+ mAppInfoAdapter.sortList(mSortOrder);
+ //load resources now
+ if(mResourceThread.isAlive()) {
+ mResourceThread.interrupt();
}
+ mResourceThread.loadAllResources(mAppInfoAdapter.getAppList());
}
- createListFromValues();
}
break;
case REMOVE_PKG:
- if(!mDoneIniting) {
- //insert message again after some delay
- sendMessageToHandler(REMOVE_PKG, data, 10*1000);
+ if(localLOGV) Log.i(TAG, "Message REMOVE_PKG");
+ if(pkgName == null) {
+ Log.w(TAG, "Ignoring message:REMOVE_PKG for null pkgName");
break;
}
- pkgName = data.getString(ATTR_PKG_NAME);
- removePackageFromAppList(pkgName);
- if(mSizeMap.remove(pkgName) == null) {
- Log.i(TAG, "Coudnt remove from size map package:"+pkgName);
- }
- if(mAppAdapterMap.remove(pkgName) == null) {
- Log.i(TAG, "Coudnt remove from app adapter map package:"+pkgName);
- }
- if(mCurrentPkg.packageName.equalsIgnoreCase(pkgName)) {
- if(mCurrentPkgIdx == (mAppList.size()-1)) {
- mCurrentPkgIdx--;
+ if (!mDoneIniting) {
+ Boolean currB = mAddRemoveMap.get(pkgName);
+ if (currB == null || (currB.equals(Boolean.TRUE))) {
+ mAddRemoveMap.put(pkgName, Boolean.FALSE);
}
- mCurrentPkg = mAppList.get(mCurrentPkgIdx);
+ break;
}
- createListFromValues();
+ List<String> pkgList = new ArrayList<String>();
+ pkgList.add(pkgName);
+ mAppInfoAdapter.removeFromList(pkgList);
break;
case REORDER_LIST:
- int sortOrder = msg.arg1;
- if(sortOrder != mSortOrder) {
- mSortOrder = sortOrder;
- if(localLOGV) Log.i(TAG, "Changing sort order to "+mSortOrder);
- sortAppList();
- mCurrentPkgIdx = 0;
- mCurrentPkg = mAppList.get(mCurrentPkgIdx);
- createListFromValues();
+ if(localLOGV) Log.i(TAG, "Message REORDER_LIST");
+ int menuOption = msg.arg1;
+ if((menuOption == SORT_ORDER_ALPHA) ||
+ (menuOption == SORT_ORDER_SIZE)) {
+ // Option to sort list
+ if (menuOption != mSortOrder) {
+ mSortOrder = menuOption;
+ if (localLOGV) Log.i(TAG, "Changing sort order to "+mSortOrder);
+ mAppInfoAdapter.sortList(mSortOrder);
+ }
+ } else if(menuOption != mFilterApps) {
+ // Option to filter list
+ mFilterApps = menuOption;
+ boolean ret = mAppInfoAdapter.resetAppList(mFilterApps,
+ getInstalledApps(mFilterApps));
+ if(!ret) {
+ // Reset cache
+ mAppPropCache = null;
+ mFilterApps = FILTER_APPS_ALL;
+ mHandler.sendEmptyMessage(COMPUTE_PKG_SIZE_START);
+ sendMessageToHandler(REORDER_LIST, menuOption);
+ }
}
break;
- case ADD_PKG:
- pkgName = data.getString(ATTR_PKG_NAME);
- if(!mDoneIniting) {
- //insert message again after some delay
- sendMessageToHandler(ADD_PKG, data, 10*1000);
+ case ADD_PKG_START:
+ if(localLOGV) Log.i(TAG, "Message ADD_PKG_START");
+ if(pkgName == null) {
+ Log.w(TAG, "Ignoring message:ADD_PKG_START for null pkgName");
break;
}
- mObserver.invokeGetSizeInfo(pkgName, false);
+ if (!mDoneIniting) {
+ Boolean currB = mAddRemoveMap.get(pkgName);
+ if (currB == null || (currB.equals(Boolean.FALSE))) {
+ mAddRemoveMap.put(pkgName, Boolean.TRUE);
+ }
+ break;
+ }
+ try {
+ info = mPm.getApplicationInfo(pkgName, 0);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Couldnt find application info for:"+pkgName);
+ break;
+ }
+ mObserver.invokeGetSizeInfo(info, ADD_PKG_DONE);
break;
+ case ADD_PKG_DONE:
+ if(localLOGV) Log.i(TAG, "Message COMPUTE_PKG_SIZE_DONE");
+ if(pkgName == null) {
+ Log.w(TAG, "Ignoring message:ADD_PKG_START for null pkgName");
+ break;
+ }
+ ps = data.getParcelable(ATTR_APP_PKG_STATS);
+ mAppInfoAdapter.addToList(pkgName, ps);
+ break;
+ case REFRESH_ICONS:
+ Map<String, AppInfo> iconMap = (Map<String, AppInfo>) msg.obj;
+ if(iconMap == null) {
+ Log.w(TAG, "Error loading icons for applications");
+ } else {
+ mAppInfoAdapter.updateAppsResourceInfo(iconMap);
+ setProgressBarIndeterminateVisibility(false);
+ }
default:
break;
}
}
};
- private void removePackageFromAppList(String pkgName) {
- int size = mAppList.size();
- for(int i = 0; i < size; i++) {
- if(mAppList.get(i).packageName.equalsIgnoreCase(pkgName)) {
- mAppList.remove(i);
- break;
+ List<ApplicationInfo> getInstalledApps(int filterOption) {
+ List<ApplicationInfo> installedAppList = mPm.getInstalledApplications(
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ if (installedAppList == null) {
+ return new ArrayList<ApplicationInfo> ();
+ }
+ if (filterOption == FILTER_APPS_THIRD_PARTY) {
+ List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> ();
+ for (ApplicationInfo appInfo : installedAppList) {
+ if ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ appList.add(appInfo);
+ }
+ }
+ return appList;
+ } else if (filterOption == FILTER_APPS_RUNNING) {
+ List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> ();
+ List<ActivityManager.RunningAppProcessInfo> procList = getRunningAppProcessesList();
+ if ((procList == null) || (procList.size() == 0)) {
+ return appList;
}
+ // Retrieve running processes from ActivityManager
+ for (ActivityManager.RunningAppProcessInfo appProcInfo : procList) {
+ if ((appProcInfo != null) && (appProcInfo.pkgList != null)){
+ int size = appProcInfo.pkgList.length;
+ for (int i = 0; i < size; i++) {
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mPm.getApplicationInfo(appProcInfo.pkgList[i],
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Error retrieving ApplicationInfo for pkg:"+appProcInfo.pkgList[i]);
+ continue;
+ }
+ if(appInfo != null) {
+ appList.add(appInfo);
+ }
+ }
+ }
+ }
+ return appList;
+ } else {
+ return installedAppList;
}
}
- private void clearMessages() {
- synchronized(mHandler) {
- mHandler.removeMessages(COMPUTE_PKG_SIZE_START);
- mHandler.removeMessages(COMPUTE_PKG_SIZE_DONE);
- mHandler.removeMessages(REMOVE_PKG);
- mHandler.removeMessages(REORDER_LIST);
- mHandler.removeMessages(ADD_PKG);
+ private List<ActivityManager.RunningAppProcessInfo> getRunningAppProcessesList() {
+ ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+ return am.getRunningAppProcesses();
+ }
+
+ // some initialization code used when kicking off the size computation
+ private void initAppList(int filterOption) {
+ mDoneIniting = false;
+ // Initialize lists
+ List<ApplicationInfo> appList = getInstalledApps(filterOption);
+ mAddRemoveMap = new TreeMap<String, Boolean>();
+ mAppInfoAdapter = new AppInfoAdapter(this, appList);
+ dismissLoadingMsg();
+ // get list and set listeners and adapter
+ ListView lv= (ListView) findViewById(android.R.id.list);
+ lv.setOnItemClickListener(this);
+ lv.setSaveEnabled(true);
+ lv.setItemsCanFocus(true);
+ lv.setOnItemClickListener(this);
+ lv.setAdapter(mAppInfoAdapter);
+ // register receiver
+ mReceiver = new PackageIntentReceiver();
+ mReceiver.registerReceiver();
+ // initiate compute pkg sizes
+ if (localLOGV) Log.i(TAG, "Initiating compute sizes for first time");
+ mObserver = new PkgSizeObserver();
+ if(appList.size() > 0) {
+ mObserver.invokeGetSizeInfo(appList.get(0), COMPUTE_PKG_SIZE_DONE);
+ } else {
+ mDoneIniting = true;
}
}
- private void sendMessageToHandler(int msgId, Bundle data, long delayMillis) {
- synchronized(mHandler) {
- Message msg = mHandler.obtainMessage(msgId);
- msg.setData(data);
- if(delayMillis == 0) {
- mHandler.sendMessage(msg);
- } else {
- mHandler.sendMessageDelayed(msg, delayMillis);
- }
+ // internal structure used to track added and deleted packages when
+ // the activity has focus
+ class AddRemoveInfo {
+ String pkgName;
+ boolean add;
+ public AddRemoveInfo(String pPkgName, boolean pAdd) {
+ pkgName = pPkgName;
+ add = pAdd;
}
}
- private void sendMessageToHandler(int msgId, int arg1) {
- synchronized(mHandler) {
- Message msg = mHandler.obtainMessage(msgId);
- msg.arg1 = arg1;
+ class ResourceLoaderThread extends Thread {
+ List<ApplicationInfo> mAppList;
+
+ void loadAllResources(List<ApplicationInfo> appList) {
+ if(appList == null || appList.size() <= 0) {
+ Log.w(TAG, "Empty or null application list");
+ return;
+ }
+ mAppList = appList;
+ start();
+ }
+
+ public void run() {
+ Map<String, AppInfo> iconMap = new HashMap<String, AppInfo>();
+ for (ApplicationInfo appInfo : mAppList) {
+ CharSequence appName = appInfo.loadLabel(mPm);
+ Drawable appIcon = appInfo.loadIcon(mPm);
+ iconMap.put(appInfo.packageName,
+ new AppInfo(appInfo.packageName, appName, appIcon));
+ }
+ Message msg = mHandler.obtainMessage(REFRESH_ICONS);
+ msg.obj = iconMap;
mHandler.sendMessage(msg);
}
}
- private void sendMessageToHandler(int msgId) {
- synchronized(mHandler) {
- mHandler.sendEmptyMessage(msgId);
+ /* Internal class representing an application or packages displayable attributes
+ *
+ */
+ class AppInfo {
+ public String pkgName;
+ int index;
+ public CharSequence appName;
+ public Drawable appIcon;
+ public CharSequence appSize;
+ public PackageStats appStats;
+
+ public void refreshIcon(AppInfo pInfo) {
+ appName = pInfo.appName;
+ appIcon = pInfo.appIcon;
+ }
+
+ public AppInfo(String pName, CharSequence aName, Drawable aIcon) {
+ index = -1;
+ pkgName = pName;
+ appName = aName;
+ appIcon = aIcon;
+ appStats = null;
+ appSize = mComputingSizeStr;
+ }
+
+ public AppInfo(String pName, int pIndex, CharSequence aName, Drawable aIcon,
+ PackageStats ps) {
+ index = pIndex;
+ pkgName = pName;
+ appName = aName;
+ appIcon = aIcon;
+ if(ps == null) {
+ appSize = mComputingSizeStr;
+ } else {
+ appStats = ps;
+ appSize = getSizeStr();
+ }
+ }
+ public void setSize(PackageStats ps) {
+ appStats = ps;
+ if (ps != null) {
+ appSize = getSizeStr();
+ }
+ }
+ public long getTotalSize() {
+ PackageStats ps = appStats;
+ if (ps != null) {
+ return ps.cacheSize+ps.codeSize+ps.dataSize;
+ }
+ return SIZE_INVALID;
+ }
+
+ private String getSizeStr() {
+ PackageStats ps = appStats;
+ String retStr = "";
+ // insert total size information into map to display in view
+ // at this point its guaranteed that ps is not null. but checking anyway
+ if (ps != null) {
+ long size = getTotalSize();
+ if (size == SIZE_INVALID) {
+ return mInvalidSizeStr.toString();
+ }
+ return Formatter.formatFileSize(ManageApplications.this, size);
+ }
+ return retStr;
}
}
- class PkgSizeObserver extends IPackageStatsObserver.Stub {
- public PackageStats ps;
- public ApplicationInfo appInfo;
- public Drawable appIcon;
- public CharSequence appName;
- public CharSequence appDesc = "";
- private int mIdx = 0;
- private boolean mChained = false;
- public boolean succeeded;
- PkgSizeObserver(int i) {
- mIdx = i;
+ // View Holder used when displaying views
+ static class AppViewHolder {
+ TextView appName;
+ ImageView appIcon;
+ TextView appSize;
+ }
+
+ /* Custom adapter implementation for the ListView
+ * This adapter maintains a map for each displayed application and its properties
+ * An index value on each AppInfo object indicates the correct position or index
+ * in the list. If the list gets updated dynamically when the user is viewing the list of
+ * applications, we need to return the correct index of position. This is done by mapping
+ * the getId methods via the package name into the internal maps and indices.
+ * The order of applications in the list is mirrored in mAppLocalList
+ */
+ class AppInfoAdapter extends BaseAdapter {
+ private Map<String, AppInfo> mAppPropMap;
+ private List<ApplicationInfo> mAppLocalList;
+ ApplicationInfo.DisplayNameComparator mAlphaComparator;
+ AppInfoComparator mSizeComparator;
+
+ private AppInfo getFromCache(String packageName) {
+ if(mAppPropCache == null) {
+ return null;
+ }
+ return mAppPropCache.get(packageName);
}
- private void getAppDetails() {
+ public AppInfoAdapter(Context c, List<ApplicationInfo> appList) {
+ mAppLocalList = appList;
+ boolean useCache = false;
+ int sortOrder = SORT_ORDER_ALPHA;
+ int imax = mAppLocalList.size();
+ if(mAppPropCache != null) {
+ useCache = true;
+ // Activity has been resumed. can use the cache to populate values initially
+ mAppPropMap = mAppPropCache;
+ sortOrder = mSortOrder;
+ }
+ sortAppList(sortOrder);
+ // Recreate property map
+ mAppPropMap = new TreeMap<String, AppInfo>();
+ for (int i = 0; i < imax; i++) {
+ ApplicationInfo info = mAppLocalList.get(i);
+ AppInfo aInfo = getFromCache(info.packageName);
+ if(aInfo == null){
+ aInfo = new AppInfo(info.packageName, i,
+ info.packageName, mDefaultAppIcon, null);
+ } else {
+ aInfo.index = i;
+ }
+ mAppPropMap.put(info.packageName, aInfo);
+ }
+ }
+
+ public int getCount() {
+ return mAppLocalList.size();
+ }
+
+ public Object getItem(int position) {
+ return mAppLocalList.get(position);
+ }
+
+ /*
+ * This method returns the index of the package position in the application list
+ */
+ public int getIndex(String pkgName) {
+ if(pkgName == null) {
+ Log.w(TAG, "Getting index of null package in List Adapter");
+ }
+ int imax = mAppLocalList.size();
+ ApplicationInfo appInfo;
+ for(int i = 0; i < imax; i++) {
+ appInfo = mAppLocalList.get(i);
+ if(appInfo.packageName.equalsIgnoreCase(pkgName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public ApplicationInfo getApplicationInfo(int position) {
+ int imax = mAppLocalList.size();
+ if( (position < 0) || (position >= imax)) {
+ Log.w(TAG, "Position out of bounds in List Adapter");
+ return null;
+ }
+ return mAppLocalList.get(position);
+ }
+
+ public void addApplicationInfo(ApplicationInfo info) {
+ if(info == null) {
+ Log.w(TAG, "Ignoring null add in List Adapter");
+ return;
+ }
+ mAppLocalList.add(info);
+ }
+
+ public long getItemId(int position) {
+ int imax = mAppLocalList.size();
+ if( (position < 0) || (position >= imax)) {
+ Log.w(TAG, "Position out of bounds in List Adapter");
+ return -1;
+ }
+ return mAppPropMap.get(mAppLocalList.get(position).packageName).index;
+ }
+
+ public List<ApplicationInfo> getAppList() {
+ return mAppLocalList;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // A ViewHolder keeps references to children views to avoid unneccessary calls
+ // to findViewById() on each row.
+ AppViewHolder holder;
+
+ // When convertView is not null, we can reuse it directly, there is no need
+ // to reinflate it. We only inflate a new View when the convertView supplied
+ // by ListView is null.
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.manage_applications_item, null);
+
+ // Creates a ViewHolder and store references to the two children views
+ // we want to bind data to.
+ holder = new AppViewHolder();
+ holder.appName = (TextView) convertView.findViewById(R.id.app_name);
+ holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
+ holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
+ convertView.setTag(holder);
+ } else {
+ // Get the ViewHolder back to get fast access to the TextView
+ // and the ImageView.
+ holder = (AppViewHolder) convertView.getTag();
+ }
+
+ // Bind the data efficiently with the holder
+ ApplicationInfo appInfo = mAppLocalList.get(position);
+ AppInfo mInfo = mAppPropMap.get(appInfo.packageName);
+ if(mInfo != null) {
+ if(mInfo.appName != null) {
+ holder.appName.setText(mInfo.appName);
+ }
+ if(mInfo.appIcon != null) {
+ holder.appIcon.setImageDrawable(mInfo.appIcon);
+ }
+ holder.appSize.setText(mInfo.appSize);
+ } else {
+ Log.w(TAG, "No info for package:"+appInfo.packageName+" in property map");
+ }
+ return convertView;
+ }
+
+ private void adjustIndex() {
+ int imax = mAppLocalList.size();
+ ApplicationInfo info;
+ for (int i = 0; i < imax; i++) {
+ info = mAppLocalList.get(i);
+ mAppPropMap.get(info.packageName).index = i;
+ }
+ }
+
+ public void sortAppList(int sortOrder) {
+ Collections.sort(mAppLocalList, getAppComparator(sortOrder));
+ }
+
+ public void sortList(int sortOrder) {
+ sortAppList(sortOrder);
+ adjustIndex();
+ notifyDataSetChanged();
+ }
+
+ public boolean resetAppList(int filterOption, List<ApplicationInfo> appList) {
+ // Create application list based on the filter value
+ mAppLocalList = appList;
+ // Check for all properties in map before sorting. Populate values from cache
+ for(ApplicationInfo applicationInfo : mAppLocalList) {
+ AppInfo appInfo = mAppPropMap.get(applicationInfo.packageName);
+ if(appInfo == null) {
+ AppInfo rInfo = getFromCache(applicationInfo.packageName);
+ if(rInfo == null) {
+ // Need to load resources again. Inconsistency somewhere
+ return false;
+ }
+ mAppPropMap.put(applicationInfo.packageName, rInfo);
+ }
+ }
+ sortList(mSortOrder);
+ return true;
+ }
+
+ private Comparator<ApplicationInfo> getAppComparator(int sortOrder) {
+ if (sortOrder == SORT_ORDER_ALPHA) {
+ // Lazy initialization
+ if (mAlphaComparator == null) {
+ mAlphaComparator = new ApplicationInfo.DisplayNameComparator(mPm);
+ }
+ return mAlphaComparator;
+ }
+ // Lazy initialization
+ if(mSizeComparator == null) {
+ mSizeComparator = new AppInfoComparator(mAppPropMap);
+ }
+ return mSizeComparator;
+ }
+
+ public void updateAppsResourceInfo(Map<String, AppInfo> iconMap) {
+ if(iconMap == null) {
+ Log.w(TAG, "Null iconMap when refreshing icon in List Adapter");
+ return;
+ }
+ boolean changed = false;
+ for (ApplicationInfo info : mAppLocalList) {
+ AppInfo pInfo = iconMap.get(info.packageName);
+ if(pInfo != null) {
+ AppInfo aInfo = mAppPropMap.get(info.packageName);
+ aInfo.refreshIcon(pInfo);
+ changed = true;
+ }
+ }
+ if(changed) {
+ notifyDataSetChanged();
+ }
+ }
+
+ public void addToList(String pkgName, PackageStats ps) {
+ if(pkgName == null) {
+ Log.w(TAG, "Adding null pkg to List Adapter");
+ return;
+ }
+ ApplicationInfo info;
try {
- appInfo = mPm.getApplicationInfo(ps.packageName, 0);
+ info = mPm.getApplicationInfo(pkgName, 0);
} catch (NameNotFoundException e) {
+ Log.w(TAG, "Ignoring non-existent package:"+pkgName);
+ return;
+ }
+ if(info == null) {
+ // Nothing to do log error message and return
+ Log.i(TAG, "Null ApplicationInfo for package:"+pkgName);
+ return;
+ }
+ // Binary search returns a negative index (ie --index) of the position where
+ // this might be inserted.
+ int newIdx = Collections.binarySearch(mAppLocalList, info,
+ getAppComparator(mSortOrder));
+ if(newIdx >= 0) {
+ Log.i(TAG, "Strange. Package:"+pkgName+" is not new");
return;
}
- appName = appInfo.loadLabel(mPm);
- appIcon = appInfo.loadIcon(mPm);
+ // New entry
+ newIdx = -newIdx-1;
+ mAppLocalList.add(newIdx, info);
+ mAppPropMap.put(info.packageName, new AppInfo(pkgName, newIdx,
+ info.loadLabel(mPm), info.loadIcon(mPm), ps));
+ adjustIndex();
+ notifyDataSetChanged();
}
+ public void removeFromList(List<String> pkgNames) {
+ if(pkgNames == null) {
+ Log.w(TAG, "Removing null pkg list from List Adapter");
+ return;
+ }
+ int imax = mAppLocalList.size();
+ boolean found = false;
+ ApplicationInfo info;
+ int i, k;
+ String pkgName;
+ int kmax = pkgNames.size();
+ if(kmax <= 0) {
+ Log.w(TAG, "Removing empty pkg list from List Adapter");
+ return;
+ }
+ int idxArr[] = new int[kmax];
+ for (k = 0; k < kmax; k++) {
+ idxArr[k] = -1;
+ }
+ for (i = 0; i < imax; i++) {
+ info = mAppLocalList.get(i);
+ for (k = 0; k < kmax; k++) {
+ pkgName = pkgNames.get(k);
+ if (info.packageName.equalsIgnoreCase(pkgName)) {
+ idxArr[k] = i;
+ found = true;
+ break;
+ }
+ }
+ }
+ // Sort idxArr
+ Arrays.sort(idxArr);
+ // remove the packages based on decending indices
+ for (k = kmax-1; k >= 0; k--) {
+ // Check if package has been found in the list of existing apps first
+ if(idxArr[k] == -1) {
+ break;
+ }
+ info = mAppLocalList.get(idxArr[k]);
+ mAppLocalList.remove(idxArr[k]);
+ mAppPropMap.remove(info.packageName);
+ if (localLOGV) Log.i(TAG, "Removed pkg:"+info.packageName+ " list");
+ }
+ if (found) {
+ adjustIndex();
+ notifyDataSetChanged();
+ }
+ }
+
+ public void updateAppSize(String pkgName, PackageStats ps) {
+ if(pkgName == null) {
+ return;
+ }
+ AppInfo entry = mAppPropMap.get(pkgName);
+ if (entry == null) {
+ Log.w(TAG, "Entry for package:"+pkgName+"doesnt exist in map");
+ return;
+ }
+ // Copy the index into the newly updated entry
+ entry.setSize(ps);
+ notifyDataSetChanged();
+ }
+
+ public PackageStats getAppStats(String pkgName) {
+ if(pkgName == null) {
+ return null;
+ }
+ AppInfo entry = mAppPropMap.get(pkgName);
+ if (entry == null) {
+ return null;
+ }
+ return entry.appStats;
+ }
+ }
+
+ /*
+ * Utility method to clear messages to Handler
+ * We need'nt synchronize on the Handler since posting messages is guaranteed
+ * to be thread safe. Even if the other thread that retrieves package sizes
+ * posts a message, we do a cursory check of validity on mAppInfoAdapter's applist
+ */
+ private void clearMessagesInHandler() {
+ mHandler.removeMessages(COMPUTE_PKG_SIZE_START);
+ mHandler.removeMessages(COMPUTE_PKG_SIZE_DONE);
+ mHandler.removeMessages(REMOVE_PKG);
+ mHandler.removeMessages(REORDER_LIST);
+ mHandler.removeMessages(ADD_PKG_START);
+ mHandler.removeMessages(ADD_PKG_DONE);
+ }
+
+ private void sendMessageToHandler(int msgId, int arg1) {
+ Message msg = mHandler.obtainMessage(msgId);
+ msg.arg1 = arg1;
+ mHandler.sendMessage(msg);
+ }
+
+ private void sendMessageToHandler(int msgId, Bundle data) {
+ Message msg = mHandler.obtainMessage(msgId);
+ msg.setData(data);
+ mHandler.sendMessage(msg);
+ }
+
+ private void sendMessageToHandler(int msgId) {
+ mHandler.sendEmptyMessage(msgId);
+ }
+
+ /*
+ * Stats Observer class used to compute package sizes and retrieve size information
+ * PkgSizeOberver is the call back thats used when invoking getPackageSizeInfo on
+ * PackageManager. The values in call back onGetStatsCompleted are validated
+ * and the specified message is passed to mHandler. The package name
+ * and the AppInfo object corresponding to the package name are set on the message
+ */
+ class PkgSizeObserver extends IPackageStatsObserver.Stub {
+ private ApplicationInfo mAppInfo;
+ private int mMsgId;
public void onGetStatsCompleted(PackageStats pStats, boolean pSucceeded) {
- Bundle data = new Bundle();
- ps = pStats;
- succeeded = pSucceeded;
- if(mChained) {
- data.putInt(ATTR_APP_IDX, mIdx);
- if(succeeded) {
- mIdx++;
+ if(DEBUG_PKG_DELAY) {
+ try {
+ Thread.sleep(10*1000);
+ } catch (InterruptedException e) {
}
}
- data.putBoolean(ATTR_CHAINED, mChained);
- getAppDetails();
- if(localLOGV) Log.i(TAG, "onGetStatsCompleted::"+appInfo.packageName+", ("+ps.cacheSize+","+
- ps.codeSize+", "+ps.dataSize);
- sendMessageToHandler(COMPUTE_PKG_SIZE_DONE, data, 0);
+ AppInfo appInfo = null;
+ Bundle data = new Bundle();
+ data.putString(ATTR_PKG_NAME, mAppInfo.packageName);
+ if(pSucceeded && pStats != null) {
+ if (localLOGV) Log.i(TAG, "onGetStatsCompleted::"+pStats.packageName+", ("+
+ pStats.cacheSize+","+
+ pStats.codeSize+", "+pStats.dataSize);
+ data.putParcelable(ATTR_APP_PKG_STATS, pStats);
+ } else {
+ Log.w(TAG, "Invalid package stats from PackageManager");
+ }
+ //post message to Handler
+ Message msg = mHandler.obtainMessage(mMsgId, data);
+ msg.setData(data);
+ mHandler.sendMessage(msg);
}
-
- public void invokeGetSizeInfo(String packageName, boolean chained) {
- mChained = chained;
- mPm.getPackageSizeInfo(packageName, this);
+
+ public void invokeGetSizeInfo(ApplicationInfo pAppInfo, int msgId) {
+ if(pAppInfo == null || pAppInfo.packageName == null) {
+ return;
+ }
+ if(localLOGV) Log.i(TAG, "Invoking getPackageSizeInfo for package:"+
+ pAppInfo.packageName);
+ mMsgId = msgId;
+ mAppInfo = pAppInfo;
+ mPm.getPackageSizeInfo(pAppInfo.packageName, this);
}
}
@@ -360,239 +998,147 @@ public class ManageApplications extends Activity implements SimpleAdapter.ViewBi
String actionStr = intent.getAction();
Uri data = intent.getData();
String pkgName = data.getEncodedSchemeSpecificPart();
- if(localLOGV) Log.i(TAG, "action:"+actionStr+", for package:"+pkgName);
+ if (localLOGV) Log.i(TAG, "action:"+actionStr+", for package:"+pkgName);
updatePackageList(actionStr, pkgName);
}
}
private void updatePackageList(String actionStr, String pkgName) {
- //technically we dont have to invoke handler since onReceive is invoked on
- //the main thread but doing it here for better clarity
- if(Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) {
+ // technically we dont have to invoke handler since onReceive is invoked on
+ // the main thread but doing it here for better clarity
+ if (Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) {
Bundle data = new Bundle();
data.putString(ATTR_PKG_NAME, pkgName);
- sendMessageToHandler(ADD_PKG, data, 0);
- } else if(Intent.ACTION_PACKAGE_REMOVED.equalsIgnoreCase(actionStr)) {
+ sendMessageToHandler(ADD_PKG_START, data);
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equalsIgnoreCase(actionStr)) {
Bundle data = new Bundle();
data.putString(ATTR_PKG_NAME, pkgName);
- sendMessageToHandler(REMOVE_PKG, data, 0);
- } else if(Intent.ACTION_PACKAGE_CHANGED.equalsIgnoreCase(actionStr)) {
- //force adapter to draw the list again. TODO derive from SimpleAdapter
- //to avoid this
-
- }
- }
-
- /*
- * Utility method to create an array of map objects from a map of map objects
- * for displaying list items to be used in SimpleAdapter.
- */
- private void createListFromValues() {
- findViewById(R.id.center_text).setVisibility(View.GONE);
- populateAdapterList();
- mAppAdapter.setViewBinder(this);
- ListView lv= (ListView) findViewById(android.R.id.list);
- lv.setOnItemClickListener(this);
- lv.setAdapter(mAppAdapter);
- if(mCurrentPkgIdx != -1) {
- lv.setSelection(mCurrentPkgIdx);
+ sendMessageToHandler(REMOVE_PKG, data);
}
}
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- String action = getIntent().getAction();
- if(action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
+ Intent lIntent = getIntent();
+ String action = lIntent.getAction();
+ if (action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
mSortOrder = SORT_ORDER_SIZE;
}
mPm = getPackageManager();
- //load strings from resources
- mBStr = getString(R.string.b_text);
- mKbStr = getString(R.string.kb_text);
- mMbStr = getString(R.string.mb_text);
+ // initialize some window features
+ requestWindowFeature(Window.FEATURE_RIGHT_ICON);
+ requestWindowFeature(Window.FEATURE_PROGRESS);
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+ // init mLoadingDlg
+ mLoadingDlg = new ProgressDialog(this);
+ mLoadingDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mLoadingDlg.setMessage(getText(R.string.loading));
+ mLoadingDlg.setIndeterminate(true);
+ mLoadingDlg.setOnCancelListener(this);
+ mDefaultAppIcon =Resources.getSystem().getDrawable(
+ com.android.internal.R.drawable.sym_def_app_icon);
+ mInvalidSizeStr = getText(R.string.invalid_size_value);
+ mComputingSizeStr = getText(R.string.computing_size);
+ // initialize the inflater
+ mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ private void showLoadingMsg() {
+ if (mLoadingDlg != null) {
+ if(localLOGV) Log.i(TAG, "Displaying Loading message");
+ mLoadingDlg.show();
+ }
+ }
+
+ private void dismissLoadingMsg() {
+ if ((mLoadingDlg != null) && (mLoadingDlg.isShowing())) {
+ if(localLOGV) Log.i(TAG, "Dismissing Loading message");
+ mLoadingDlg.dismiss();
+ }
}
@Override
public void onStart() {
super.onStart();
setContentView(R.layout.compute_sizes);
- //clear all messages related to application list
- clearMessages();
+ showLoadingMsg();
+ // Create a thread to load resources
+ mResourceThread = new ResourceLoaderThread();
sendMessageToHandler(COMPUTE_PKG_SIZE_START);
}
@Override
public void onStop() {
super.onStop();
- //register receiver here
+ // clear all messages related to application list
+ clearMessagesInHandler();
+ // register receiver here
unregisterReceiver(mReceiver);
+ mAppPropCache = mAppInfoAdapter.mAppPropMap;
}
+ /*
+ * comparator class used to sort AppInfo objects based on size
+ */
public static class AppInfoComparator implements Comparator<ApplicationInfo> {
- public AppInfoComparator(HashMap<String, PackageStats> pSizeMap) {
- mSizeMap= pSizeMap;
+ public AppInfoComparator(Map<String, AppInfo> pAppPropMap) {
+ mAppPropMap= pAppPropMap;
}
public final int compare(ApplicationInfo a, ApplicationInfo b) {
- PackageStats aps, bps;
- aps = mSizeMap.get(a.packageName);
- bps = mSizeMap.get(b.packageName);
- if (aps == null && bps == null) {
- return 0;
- } else if (aps == null) {
+ AppInfo ainfo = mAppPropMap.get(a.packageName);
+ AppInfo binfo = mAppPropMap.get(b.packageName);
+ long atotal = ainfo.getTotalSize();
+ long btotal = binfo.getTotalSize();
+ long ret = atotal - btotal;
+ // negate result to sort in descending order
+ if (ret < 0) {
return 1;
- } else if (bps == null) {
- return -1;
}
- long atotal = aps.dataSize+aps.codeSize+aps.cacheSize;
- long btotal = bps.dataSize+bps.codeSize+bps.cacheSize;
- long ret = atotal-btotal;
- //negate result to sort in descending order
- if(ret < 0) {
- return 1;
- }
- if(ret == 0) {
+ if (ret == 0) {
return 0;
}
return -1;
}
- private HashMap<String, PackageStats> mSizeMap;
+ private Map<String, AppInfo> mAppPropMap;
}
-
- /*
- * Have to extract elements form map and populate a list ot be used by
- * SimpleAdapter when displaying list elements. The sort order has to follow
- * the order of elements in mAppList.
- */
- private List<Map<String, ?>> createAdapterListFromMap() {
- //get the index from mAppInfo which gives the correct sort position
- int imax = mAppList.size();
- if(localLOGV) Log.i(TAG, "Creating new adapter list");
- List<Map<String, ?>> adapterList = new ArrayList<Map<String, ?>>();
- ApplicationInfo tmpInfo;
- for(int i = 0; i < imax; i++) {
- tmpInfo = mAppList.get(i);
- Map<String, Object>newObj = new TreeMap<String, Object>(
- mAppAdapterMap.get(tmpInfo.packageName));
- adapterList.add(newObj);
- }
- return adapterList;
- }
- private void populateAdapterList() {
- mAppAdapter = new SimpleAdapter(this, createAdapterListFromMap(),
- R.layout.manage_applications_item, sKeys, sResourceIds);
- }
-
- private String getSizeStr(PackageStats ps) {
- String retStr = "";
- //insert total size information into map to display in view
- //at this point its guaranteed that ps is not null. but checking anyway
- if(ps != null) {
- long size = ps.cacheSize+ps.codeSize+ps.dataSize;
- if(size < 1024) {
- return String.valueOf(size)+mBStr;
- }
- long kb, mb, rem;
- kb = size >> 10;
- rem = size - (kb << 10);
- if(kb < 1024) {
- if(rem > 512) {
- kb++;
- }
- retStr += String.valueOf(kb)+mKbStr;
- return retStr;
- }
- mb = kb >> 10;
- if(kb >= 512) {
- //round off
- mb++;
- }
- retStr += String.valueOf(mb)+ mMbStr;
- return retStr;
- } else {
- Log.w(TAG, "Something fishy, cannot find size info for package:"+ps.packageName);
- }
- return retStr;
- }
-
- public void sortAppList() {
- // Sort application list
- if(mSortOrder == SORT_ORDER_ALPHA) {
- Collections.sort(mAppList, new ApplicationInfo.DisplayNameComparator(mPm));
- } else if(mSortOrder == SORT_ORDER_SIZE) {
- Collections.sort(mAppList, new AppInfoComparator(mSizeMap));
- }
- }
-
- private Map<String, Object> createMapEntry(CharSequence appName,
- Drawable appIcon, CharSequence appDesc, String sizeStr) {
- Map<String, Object> map = new TreeMap<String, Object>();
- map.put(KEY_APP_NAME, appName);
- //the icon cannot be null. if the application hasnt set it, the default icon is returned.
- map.put(KEY_APP_ICON, appIcon);
- if(appDesc == null) {
- appDesc="";
- }
- map.put(KEY_APP_DESC, appDesc);
- map.put(KEY_APP_SIZE, sizeStr);
- return map;
- }
-
+ // utility method used to start sub activity
private void startApplicationDetailsActivity(ApplicationInfo info, PackageStats ps) {
- //Create intent to start new activity
+ // Create intent to start new activity
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClass(this, InstalledAppDetails.class);
- intent.putExtra(APP_PKG_NAME, info.packageName);
- if(localLOGV) Log.i(TAG, "code="+ps.codeSize+", cache="+ps.cacheSize+", data="+ps.dataSize);
- intent.putExtra(APP_PKG_SIZE, ps);
- if(localLOGV) Log.i(TAG, "Starting sub activity to display info for app:"+info
- +" with intent:"+intent);
- //start new activity to display extended information
- if ((info.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
- }
+ mCurrentPkgName = info.packageName;
+ intent.putExtra(APP_PKG_NAME, mCurrentPkgName);
+ intent.putExtra(APP_PKG_SIZE, ps);
+ // start new activity to display extended information
startActivityForResult(intent, INSTALLED_APP_DETAILS);
}
- public boolean setViewValue(View view, Object data, String textRepresentation) {
- if(data == null) {
- return false;
- }
- int id = view.getId();
- switch(id) {
- case R.id.app_name:
- ((TextView)view).setText((String)data);
- break;
- case R.id.app_icon:
- ((ImageView)view).setImageDrawable((Drawable)data);
- break;
- case R.id.app_description:
- ((TextView)view).setText((String)data);
- break;
- case R.id.app_size:
- ((TextView)view).setText((String)data);
- break;
- default:
- break;
- }
- return true;
- }
-
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(0, SORT_ORDER_ALPHA, 0, R.string.sort_order_alpha)
+ menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha)
.setIcon(android.R.drawable.ic_menu_sort_alphabetically);
- menu.add(0, SORT_ORDER_SIZE, 0, R.string.sort_order_size)
- .setIcon(android.R.drawable.ic_menu_sort_by_size);
+ menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size)
+ .setIcon(android.R.drawable.ic_menu_sort_by_size);
+ menu.add(0, FILTER_APPS_ALL, 3, R.string.filter_apps_all);
+ menu.add(0, FILTER_APPS_RUNNING, 4, R.string.filter_apps_running);
+ menu.add(0, FILTER_APPS_THIRD_PARTY, 5, R.string.filter_apps_third_party);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- if(mDoneIniting) {
+ if (mDoneIniting) {
menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA);
- menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder!= SORT_ORDER_SIZE);
+ menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE);
+ menu.findItem(FILTER_APPS_ALL).setVisible(mFilterApps != FILTER_APPS_ALL);
+ menu.findItem(FILTER_APPS_THIRD_PARTY).setVisible(
+ mFilterApps != FILTER_APPS_THIRD_PARTY);
+ menu.findItem(FILTER_APPS_RUNNING).setVisible(
+ mFilterApps != FILTER_APPS_RUNNING);
return true;
}
return false;
@@ -607,10 +1153,13 @@ public class ManageApplications extends Activity implements SimpleAdapter.ViewBi
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
- mCurrentPkgIdx=position;
- ApplicationInfo info = mAppList.get(position);
- mCurrentPkg = info;
- PackageStats ps = mSizeMap.get(info.packageName);
- startApplicationDetailsActivity(info, ps);
+ ApplicationInfo info = (ApplicationInfo)mAppInfoAdapter.getItem(position);
+ startApplicationDetailsActivity(info, mAppInfoAdapter.getAppStats(info.packageName));
+ }
+
+ // onCancel call back for dialog thats displayed when data is being loaded
+ public void onCancel(DialogInterface dialog) {
+ mLoadingDlg = null;
+ finish();
}
}
diff --git a/src/com/android/settings/ProxySelector.java b/src/com/android/settings/ProxySelector.java
index d320e73f2b..80fe3c90c9 100644
--- a/src/com/android/settings/ProxySelector.java
+++ b/src/com/android/settings/ProxySelector.java
@@ -222,7 +222,7 @@ public class ProxySelector extends Activity
if (!TextUtils.isEmpty(hostname)) {
hostname += ':' + portStr;
}
- Settings.System.putString(res, Settings.System.HTTP_PROXY, hostname);
+ Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY, hostname);
sendBroadcast(new Intent(Proxy.PROXY_CHANGE_ACTION));
return true;
diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java
index ad30de71fd..b1ad7773b5 100644
--- a/src/com/android/settings/RadioInfo.java
+++ b/src/com/android/settings/RadioInfo.java
@@ -31,13 +31,14 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.pim.DateUtils;
import android.preference.PreferenceManager;
import android.telephony.CellLocation;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.telephony.NeighboringCellInfo;
import android.telephony.gsm.GsmCellLocation;
+import android.text.format.DateUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -66,6 +67,7 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.List;
public class RadioInfo extends Activity {
@@ -210,7 +212,7 @@ public class RadioInfo extends Activity {
case EVENT_QUERY_NEIGHBORING_CIDS_DONE:
ar= (AsyncResult) msg.obj;
if (ar.exception == null) {
- updateNeighboringCids((String[])ar.result);
+ updateNeighboringCids((ArrayList<NeighboringCellInfo>)ar.result);
} else {
mNeighboringCids.setText("unknown");
}
@@ -651,23 +653,21 @@ public class RadioInfo extends Activity {
+ ((cid == -1) ? "unknown" : Integer.toHexString(cid)));
}
- private final void updateNeighboringCids(String[] cids) {
- if (cids != null && cids.length > 0 && cids[0] != null) {
- int size = Integer.parseInt(cids[0]);
- String neiborings;
- if (size > 0) {
- neiborings = "{";
- for (int i=1; i<=size; i++) {
- neiborings += cids[i] + ", ";
- }
- neiborings += "}";
+ private final void updateNeighboringCids(ArrayList<NeighboringCellInfo> cids) {
+ String neighborings = "";
+ if (cids != null) {
+ if ( cids.isEmpty() ) {
+ neighborings = "no neighboring cells";
} else {
- neiborings = "none";
+ for (NeighboringCellInfo cell : cids) {
+ neighborings += "{" + Integer.toHexString(cell.getCid())
+ + "@" + cell.getRssi() + "} ";
+ }
}
- mNeighboringCids.setText(neiborings);
} else {
- mNeighboringCids.setText("unknown");
+ neighborings = "unknown";
}
+ mNeighboringCids.setText(neighborings);
}
private final void
@@ -952,13 +952,15 @@ public class RadioInfo extends Activity {
.append("\n to ")
.append(pdp.getApn().toString())
.append("\ninterface: ")
- .append(phone.getInterfaceName(phone.getActiveApn()))
+ .append(phone.getInterfaceName(phone.getActiveApnTypes()[0]))
.append("\naddress: ")
- .append(phone.getIpAddress(phone.getActiveApn()))
+ .append(phone.getIpAddress(phone.getActiveApnTypes()[0]))
.append("\ngateway: ")
- .append(phone.getGateway(phone.getActiveApn()));
- String[] dns = phone.getDnsServers(phone.getActiveApn());
- sb.append("\ndns: ").append(dns[0]).append(", ").append(dns[1]);
+ .append(phone.getGateway(phone.getActiveApnTypes()[0]));
+ String[] dns = phone.getDnsServers(phone.getActiveApnTypes()[0]);
+ if (dns != null) {
+ sb.append("\ndns: ").append(dns[0]).append(", ").append(dns[1]);
+ }
} else if (pdp.getState().isInactive()) {
sb.append(" disconnected with last try at ")
.append(DateUtils.timeString(pdp.getLastFailTime()))
diff --git a/src/com/android/settings/RingerVolumePreference.java b/src/com/android/settings/RingerVolumePreference.java
new file mode 100644
index 0000000000..2d21ec6e99
--- /dev/null
+++ b/src/com/android/settings/RingerVolumePreference.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.preference.VolumePreference;
+import android.preference.VolumePreference.SeekBarVolumizer;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+/**
+ * Special preference type that allows configuration of both the ring volume and
+ * notification volume.
+ */
+public class RingerVolumePreference extends VolumePreference implements
+ CheckBox.OnCheckedChangeListener {
+ private static final String TAG = "RingerVolumePreference";
+
+ private CheckBox mNotificationsUseRingVolumeCheckbox;
+ private SeekBarVolumizer mNotificationSeekBarVolumizer;
+ private TextView mNotificationVolumeTitle;
+
+ public RingerVolumePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // The always visible seekbar is for ring volume
+ setStreamType(AudioManager.STREAM_RING);
+
+ setDialogLayoutResource(R.layout.preference_dialog_ringervolume);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ mNotificationsUseRingVolumeCheckbox =
+ (CheckBox) view.findViewById(R.id.same_notification_volume);
+ mNotificationsUseRingVolumeCheckbox.setOnCheckedChangeListener(this);
+ mNotificationsUseRingVolumeCheckbox.setChecked(Settings.System.getInt(
+ getContext().getContentResolver(),
+ Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1);
+
+ final SeekBar seekBar = (SeekBar) view.findViewById(R.id.notification_volume_seekbar);
+ mNotificationSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar,
+ AudioManager.STREAM_NOTIFICATION);
+
+ mNotificationVolumeTitle = (TextView) view.findViewById(R.id.notification_volume_title);
+
+ setNotificationVolumeVisibility(!mNotificationsUseRingVolumeCheckbox.isChecked());
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (!positiveResult && mNotificationSeekBarVolumizer != null) {
+ mNotificationSeekBarVolumizer.revertVolume();
+ }
+
+ cleanup();
+ }
+
+ @Override
+ public void onActivityStop() {
+ super.onActivityStop();
+ cleanup();
+ }
+
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ setNotificationVolumeVisibility(!isChecked);
+
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATIONS_USE_RING_VOLUME, isChecked ? 1 : 0);
+
+ if (isChecked) {
+ // The user wants the notification to be same as ring, so do a
+ // one-time sync right now
+ AudioManager audioManager = (AudioManager) getContext()
+ .getSystemService(Context.AUDIO_SERVICE);
+ audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION,
+ audioManager.getStreamVolume(AudioManager.STREAM_RING), 0);
+ }
+ }
+
+ @Override
+ protected void onSampleStarting(SeekBarVolumizer volumizer) {
+ super.onSampleStarting(volumizer);
+
+ if (mNotificationSeekBarVolumizer != null && volumizer != mNotificationSeekBarVolumizer) {
+ mNotificationSeekBarVolumizer.stopSample();
+ }
+ }
+
+ private void setNotificationVolumeVisibility(boolean visible) {
+ if (mNotificationSeekBarVolumizer != null) {
+ mNotificationSeekBarVolumizer.getSeekBar().setVisibility(
+ visible ? View.VISIBLE : View.GONE);
+ mNotificationVolumeTitle.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private void cleanup() {
+ if (mNotificationSeekBarVolumizer != null) {
+ mNotificationSeekBarVolumizer.stop();
+ mNotificationSeekBarVolumizer = null;
+ }
+ }
+
+}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index c1a509ae57..a0a52a2f60 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -44,11 +44,13 @@ public class SecuritySettings extends PreferenceActivity
private static final String KEY_LOCK_ENABLED = "lockenabled";
private static final String KEY_VISIBLE_PATTERN = "visiblepattern";
+ private static final String KEY_TACTILE_FEEDBACK_ENABLED = "tactilefeedback";
private static final int CONFIRM_PATTERN_REQUEST_CODE = 55;
private LockPatternUtils mLockPatternUtils;
private CheckBoxPreference mLockEnabled;
private CheckBoxPreference mVisiblePattern;
+ private CheckBoxPreference mTactileFeedback;
private Preference mChoosePattern;
private CheckBoxPreference mShowPassword;
@@ -103,6 +105,12 @@ public class SecuritySettings extends PreferenceActivity
mVisiblePattern.setTitle(R.string.lockpattern_settings_enable_visible_pattern_title);
inlinePrefCat.addPreference(mVisiblePattern);
+ // tactile feedback
+ mTactileFeedback = new CheckBoxPreference(this);
+ mTactileFeedback.setKey(KEY_TACTILE_FEEDBACK_ENABLED);
+ mTactileFeedback.setTitle(R.string.lockpattern_settings_enable_tactile_feedback_title);
+ inlinePrefCat.addPreference(mTactileFeedback);
+
// change pattern lock
Intent intent = new Intent();
intent.setClassName("com.android.settings",
@@ -146,9 +154,11 @@ public class SecuritySettings extends PreferenceActivity
boolean patternExists = mLockPatternUtils.savedPatternExists();
mLockEnabled.setEnabled(patternExists);
mVisiblePattern.setEnabled(patternExists);
+ mTactileFeedback.setEnabled(patternExists);
mLockEnabled.setChecked(mLockPatternUtils.isLockPatternEnabled());
mVisiblePattern.setChecked(mLockPatternUtils.isVisiblePatternEnabled());
+ mTactileFeedback.setChecked(mLockPatternUtils.isTactileFeedbackEnabled());
int chooseStringRes = mLockPatternUtils.savedPatternExists() ?
R.string.lockpattern_settings_change_lock_pattern :
@@ -169,6 +179,8 @@ public class SecuritySettings extends PreferenceActivity
mLockPatternUtils.setLockPatternEnabled(isToggled(preference));
} else if (KEY_VISIBLE_PATTERN.equals(key)) {
mLockPatternUtils.setVisiblePatternEnabled(isToggled(preference));
+ } else if (KEY_TACTILE_FEEDBACK_ENABLED.equals(key)) {
+ mLockPatternUtils.setTactileFeedbackEnabled(isToggled(preference));
} else if (preference == mShowPassword) {
Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,
mShowPassword.isChecked() ? 1 : 0);
@@ -198,9 +210,9 @@ public class SecuritySettings extends PreferenceActivity
}
private void setProviders(String providers) {
- // Update the system setting LOCATION_PROVIDERS_ALLOWED
- Settings.System.putString(getContentResolver(),
- Settings.System.LOCATION_PROVIDERS_ALLOWED, providers);
+ // Update the secure setting LOCATION_PROVIDERS_ALLOWED
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED, providers);
if (Config.LOGV) {
Log.v("Location Accuracy", "Setting LOCATION_PROVIDERS_ALLOWED = " + providers);
}
@@ -213,8 +225,8 @@ public class SecuritySettings extends PreferenceActivity
*/
private String getAllowedProviders() {
String allowedProviders =
- Settings.System.getString(getContentResolver(),
- Settings.System.LOCATION_PROVIDERS_ALLOWED);
+ Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
if (allowedProviders == null) {
allowedProviders = "";
}
diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java
index 82eadca13d..c40dd07192 100644
--- a/src/com/android/settings/SettingsLicenseActivity.java
+++ b/src/com/android/settings/SettingsLicenseActivity.java
@@ -28,13 +28,10 @@ import android.widget.Toast;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
-import org.apache.commons.codec.binary.Base64;
-
/**
* The "dialog" that shows from "License" in the Settings app.
*/
@@ -92,27 +89,8 @@ public class SettingsLicenseActivity extends AlertActivity {
WebView webView = new WebView(this);
- if (LOGV) Log.v(TAG, "Started encode at " + System.currentTimeMillis());
- // Need to encode to base64 for WebView to load the contents properly
- String dataStr;
- try {
- byte[] base64Bytes = Base64.encodeBase64(data.toString().getBytes("ISO8859_1"));
- dataStr = new String(base64Bytes);
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Could not convert to base64", e);
- showErrorAndFinish();
- return;
- }
- if (LOGV) Log.v(TAG, "Ended encode at " + System.currentTimeMillis());
- if (LOGV) {
- Log.v(TAG, "Started test decode at " + System.currentTimeMillis());
- Base64.decodeBase64(dataStr.getBytes());
- Log.v(TAG, "Ended decode at " + System.currentTimeMillis());
- }
-
-
// Begin the loading. This will be done in a separate thread in WebView.
- webView.loadData(dataStr, "text/html", "base64");
+ webView.loadDataWithBaseURL(null, data.toString(), "text/html", "utf-8", null);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
diff --git a/src/com/android/settings/SoundAndDisplaySettings.java b/src/com/android/settings/SoundAndDisplaySettings.java
index 887fb8f70c..134e84f3ca 100644
--- a/src/com/android/settings/SoundAndDisplaySettings.java
+++ b/src/com/android/settings/SoundAndDisplaySettings.java
@@ -25,6 +25,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
@@ -32,6 +34,7 @@ import android.preference.PreferenceScreen;
import android.preference.CheckBoxPreference;
import android.provider.Settings;
import android.util.Log;
+import android.view.IWindowManager;
public class SoundAndDisplaySettings extends PreferenceActivity implements
Preference.OnPreferenceChangeListener {
@@ -45,14 +48,19 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements
private static final String KEY_SCREEN_TIMEOUT = "screen_timeout";
private static final String KEY_DTMF_TONE = "dtmf_tone";
private static final String KEY_SOUND_EFFECTS = "sound_effects";
+ private static final String KEY_ANIMATIONS = "animations";
private CheckBoxPreference mSilent;
private CheckBoxPreference mVibrate;
private CheckBoxPreference mDtmfTone;
private CheckBoxPreference mSoundEffects;
+ private CheckBoxPreference mAnimations;
+ private float[] mAnimationScales;
private AudioManager mAudioManager;
+ private IWindowManager mWindowManager;
+
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -73,6 +81,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements
ContentResolver resolver = getContentResolver();
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
addPreferencesFromResource(R.xml.sound_and_display_settings);
@@ -86,11 +95,13 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements
mSoundEffects.setPersistent(false);
mSoundEffects.setChecked(Settings.System.getInt(resolver,
Settings.System.SOUND_EFFECTS_ENABLED, 0) != 0);
+ mAnimations = (CheckBoxPreference) findPreference(KEY_ANIMATIONS);
+ mAnimations.setPersistent(false);
ListPreference screenTimeoutPreference =
(ListPreference) findPreference(KEY_SCREEN_TIMEOUT);
screenTimeoutPreference.setValue(String.valueOf(Settings.System.getInt(
- getContentResolver(), SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE)));
+ resolver, SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE)));
screenTimeoutPreference.setOnPreferenceChangeListener(this);
}
@@ -124,6 +135,23 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements
if (phoneVibrate != mVibrate.isChecked() || force) {
mVibrate.setChecked(phoneVibrate);
}
+
+ boolean animations = true;
+ try {
+ mAnimationScales = mWindowManager.getAnimationScales();
+ } catch (RemoteException e) {
+ }
+ if (mAnimationScales != null) {
+ for (int i=0; i<mAnimationScales.length; i++) {
+ if (mAnimationScales[i] == 0) {
+ animations = false;
+ break;
+ }
+ }
+ }
+ if (animations != mAnimations.isChecked() || force) {
+ mAnimations.setChecked(animations);
+ }
}
@Override
@@ -151,6 +179,15 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements
}
Settings.System.putInt(getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED,
mSoundEffects.isChecked() ? 1 : 0);
+
+ } else if (preference == mAnimations) {
+ for (int i=0; i<mAnimationScales.length; i++) {
+ mAnimationScales[i] = mAnimations.isChecked() ? 1 : 0;
+ }
+ try {
+ mWindowManager.setAnimationScales(mAnimationScales);
+ } catch (RemoteException e) {
+ }
}
return true;
}
diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java
index 18b30bddc2..d1129150b9 100644
--- a/src/com/android/settings/WirelessSettings.java
+++ b/src/com/android/settings/WirelessSettings.java
@@ -16,25 +16,13 @@
package com.android.settings;
+import com.android.settings.bluetooth.BluetoothEnabler;
import com.android.settings.wifi.WifiEnabler;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothIntent;
-import android.bluetooth.IBluetoothDeviceCallback;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.net.wifi.WifiManager;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.preference.Preference;
import android.preference.PreferenceActivity;
-import android.preference.PreferenceScreen;
import android.preference.CheckBoxPreference;
-import android.provider.Settings;
-import android.widget.Toast;
public class WirelessSettings extends PreferenceActivity {
@@ -44,13 +32,7 @@ public class WirelessSettings extends PreferenceActivity {
private WifiEnabler mWifiEnabler;
private AirplaneModeEnabler mAirplaneModeEnabler;
-
- private CheckBoxPreference mToggleBluetooth;
-
- private IntentFilter mIntentFilter;
-
- private static final int EVENT_FAILED_BT_ENABLE = 1;
- private static final int EVENT_PASSED_BT_ENABLE = 2;
+ private BluetoothEnabler mBtEnabler;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -58,30 +40,25 @@ public class WirelessSettings extends PreferenceActivity {
addPreferencesFromResource(R.xml.wireless_settings);
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(BluetoothIntent.ENABLED_ACTION);
- mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
-
initToggles();
}
@Override
protected void onResume() {
super.onResume();
- refreshToggles();
- registerReceiver(mReceiver, mIntentFilter);
mWifiEnabler.resume();
mAirplaneModeEnabler.resume();
+ mBtEnabler.resume();
}
@Override
protected void onPause() {
super.onPause();
- unregisterReceiver(mReceiver);
-
+
mWifiEnabler.pause();
mAirplaneModeEnabler.pause();
+ mBtEnabler.pause();
}
private void initToggles() {
@@ -95,116 +72,9 @@ public class WirelessSettings extends PreferenceActivity {
this,
(CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE));
- mToggleBluetooth = (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH);
- mToggleBluetooth.setPersistent(false);
- }
-
- private void refreshToggles() {
- mToggleBluetooth.setChecked(isBluetoothEnabled());
- mToggleBluetooth.setEnabled(true);
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
- if (preference == mToggleBluetooth) {
- setBluetoothEnabled(mToggleBluetooth.isChecked());
- return true;
- }
-
- return false;
- }
-
- private boolean isBluetoothEnabled() {
- BluetoothDevice device = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
- if (device != null) {
- return device.isEnabled();
- } else {
- return false;
- }
- }
-
- private void setBluetoothEnabled(boolean enabled) {
- try {
- BluetoothDevice device = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
- if (enabled) {
- // Turn it off until intent or callback is delivered
- mToggleBluetooth.setChecked(false);
- if (device.enable(mBtCallback)) {
- mToggleBluetooth.setSummary(R.string.bluetooth_enabling);
- mToggleBluetooth.setEnabled(false);
- }
- } else {
- if (device.disable()) {
- Settings.System.putInt(getContentResolver(),
- Settings.System.BLUETOOTH_ON, 0);
- } else {
- // Unusual situation, that you can't turn off bluetooth
- mToggleBluetooth.setChecked(true);
- }
- }
- } catch (NullPointerException e) {
- // TODO: 1071858
- mToggleBluetooth.setChecked(false);
- mToggleBluetooth.setEnabled(false);
- }
- }
-
- private IBluetoothDeviceCallback mBtCallback = new IBluetoothDeviceCallback.Stub() {
-
- public void onEnableResult(int res) {
- switch (res) {
- case BluetoothDevice.RESULT_FAILURE:
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_FAILED_BT_ENABLE, 0));
- break;
- case BluetoothDevice.RESULT_SUCCESS:
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_PASSED_BT_ENABLE, 0));
- break;
- }
- }
-
- public void onCreateBondingResult(String device, int res) {
- // Don't care
- }
- public void onGetRemoteServiceChannelResult(String address, int channel) { }
- };
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
- updateBtStatus(true);
- } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
- mToggleBluetooth.setChecked(false);
- }
- }
- };
-
- private void updateBtStatus(boolean enabled) {
- mToggleBluetooth.setChecked(enabled);
- mToggleBluetooth.setEnabled(true);
- mToggleBluetooth.setSummary(R.string.bluetooth_quick_toggle_summary);
- if (enabled) {
- Settings.System.putInt(getContentResolver(),
- Settings.System.BLUETOOTH_ON, 1);
- }
+ mBtEnabler = new BluetoothEnabler(
+ this,
+ (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH));
}
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_PASSED_BT_ENABLE:
- updateBtStatus(true);
- break;
- case EVENT_FAILED_BT_ENABLE:
- updateBtStatus(false);
- Toast.makeText(WirelessSettings.this,
- getResources().getString(R.string.bluetooth_failed_to_enable),
- Toast.LENGTH_SHORT).show();
-
- break;
- }
- }
- };
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
new file mode 100644
index 0000000000..f0a818909a
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * BluetoothDevicePreference is the preference type used to display each remote
+ * Bluetooth device in the Bluetooth Settings screen.
+ */
+public class BluetoothDevicePreference extends Preference implements LocalBluetoothDevice.Callback {
+ private static final String TAG = "BluetoothDevicePreference";
+
+ private static int sDimAlpha = Integer.MIN_VALUE;
+
+ private LocalBluetoothDevice mLocalDevice;
+
+ /**
+ * Cached local copy of whether the device is busy. This is only updated
+ * from {@link #onDeviceAttributesChanged(LocalBluetoothDevice)}.
+ */
+ private boolean mIsBusy;
+
+ public BluetoothDevicePreference(Context context, LocalBluetoothDevice localDevice) {
+ 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);
+ }
+
+ mLocalDevice = localDevice;
+
+ setLayoutResource(R.layout.preference_bluetooth);
+
+ localDevice.registerCallback(this);
+
+ onDeviceAttributesChanged(localDevice);
+ }
+
+ public LocalBluetoothDevice getDevice() {
+ return mLocalDevice;
+ }
+
+ @Override
+ protected void onPrepareForRemoval() {
+ super.onPrepareForRemoval();
+ mLocalDevice.unregisterCallback(this);
+ }
+
+ public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+
+ /*
+ * The preference framework takes care of making sure the value has
+ * changed before proceeding.
+ */
+
+ setTitle(mLocalDevice.getName());
+
+ /*
+ * TODO: Showed "Paired" even though it was "Connected". This may be
+ * related to BluetoothHeadset not bound to the actual
+ * BluetoothHeadsetService when we got here.
+ */
+ setSummary(mLocalDevice.getSummary());
+
+ // Used to gray out the item
+ mIsBusy = mLocalDevice.isBusy();
+
+ // Data has changed
+ notifyChanged();
+
+ // This could affect ordering, so notify that also
+ notifyHierarchyChanged();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && !mIsBusy;
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ ImageView btClass = (ImageView) view.findViewById(R.id.btClass);
+ btClass.setImageResource(mLocalDevice.getBtClassDrawable());
+ btClass.setAlpha(isEnabled() ? 255 : sDimAlpha);
+ }
+
+ @Override
+ public int compareTo(Preference another) {
+ if (!(another instanceof BluetoothDevicePreference)) {
+ // Put other preference types above us
+ return 1;
+ }
+
+ return mLocalDevice.compareTo(((BluetoothDevicePreference) another).mLocalDevice);
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
new file mode 100644
index 0000000000..f8956967c1
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.CheckBoxPreference;
+import android.util.Log;
+
+/**
+ * BluetoothDiscoverableEnabler is a helper to manage the "Discoverable"
+ * checkbox. It sets/unsets discoverability and keeps track of how much time
+ * until the the discoverability is automatically turned off.
+ */
+public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BluetoothDiscoverableEnabler";
+ private static final boolean V = LocalBluetoothManager.V;
+
+ private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT =
+ "debug.bt.discoverable_time";
+ private static final int DISCOVERABLE_TIMEOUT = 120;
+
+ private static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP =
+ "discoverable_end_timestamp";
+
+ private final Context mContext;
+ private final Handler mUiHandler;
+ private final CheckBoxPreference mCheckBoxPreference;
+
+ private final LocalBluetoothManager mLocalManager;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleModeChanged(intent.getIntExtra(BluetoothIntent.MODE,
+ BluetoothDevice.MODE_UNKNOWN));
+ }
+ };
+
+ private final Runnable mUpdateCountdownSummaryRunnable = new Runnable() {
+ public void run() {
+ updateCountdownSummary();
+ }
+ };
+
+ public BluetoothDiscoverableEnabler(Context context, CheckBoxPreference checkBoxPreference) {
+ mContext = context;
+ mUiHandler = new Handler();
+ mCheckBoxPreference = checkBoxPreference;
+
+ checkBoxPreference.setPersistent(false);
+
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+ if (mLocalManager == null) {
+ // Bluetooth not supported
+ checkBoxPreference.setEnabled(false);
+ }
+ }
+
+ public void resume() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(BluetoothIntent.MODE_CHANGED_ACTION));
+ mCheckBoxPreference.setOnPreferenceChangeListener(this);
+
+ handleModeChanged(mLocalManager.getBluetoothManager().getMode());
+ }
+
+ public void pause() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
+ mCheckBoxPreference.setOnPreferenceChangeListener(null);
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ if (V) {
+ Log.v(TAG, "Preference changed to " + value);
+ }
+
+ // Turn on/off BT discoverability
+ setEnabled((Boolean) value);
+
+ return true;
+ }
+
+ private void setEnabled(final boolean enable) {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ if (enable) {
+
+ int timeout = getDiscoverableTimeout();
+ manager.setDiscoverableTimeout(timeout);
+
+ long endTimestamp = System.currentTimeMillis() + timeout * 1000;
+ persistDiscoverableEndTimestamp(endTimestamp);
+
+ manager.setMode(BluetoothDevice.MODE_DISCOVERABLE);
+ handleModeChanged(BluetoothDevice.MODE_DISCOVERABLE);
+
+ } else {
+ manager.setMode(BluetoothDevice.MODE_CONNECTABLE);
+ }
+ }
+
+ private int getDiscoverableTimeout() {
+ int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1);
+ if (timeout <= 0) {
+ timeout = DISCOVERABLE_TIMEOUT;
+ }
+
+ return timeout;
+ }
+
+ private void persistDiscoverableEndTimestamp(long endTimestamp) {
+ SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit();
+ editor.putLong(SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp);
+ editor.commit();
+ }
+
+ private void handleModeChanged(int mode) {
+ if (V) {
+ Log.v(TAG, "Got mode changed: " + mode);
+ }
+
+ if (mode == BluetoothDevice.MODE_DISCOVERABLE) {
+ mCheckBoxPreference.setChecked(true);
+ updateCountdownSummary();
+
+ } else {
+ mCheckBoxPreference.setChecked(false);
+ }
+ }
+
+ private void updateCountdownSummary() {
+ int mode = mLocalManager.getBluetoothManager().getMode();
+ if (mode != BluetoothDevice.MODE_DISCOVERABLE) return;
+
+ long currentTimestamp = System.currentTimeMillis();
+ long endTimestamp = mLocalManager.getSharedPreferences().getLong(
+ SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0);
+
+ if (currentTimestamp > endTimestamp) {
+ // We're still in discoverable mode, but maybe there isn't a timeout.
+ mCheckBoxPreference.setSummaryOn(null);
+ return;
+ }
+
+ String formattedTimeLeft = String.valueOf((endTimestamp - currentTimestamp) / 1000);
+
+ mCheckBoxPreference.setSummaryOn(
+ mContext.getResources().getString(R.string.bluetooth_is_discoverable,
+ formattedTimeLeft));
+
+ synchronized (this) {
+ mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
+ mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000);
+ }
+ }
+
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java
new file mode 100644
index 0000000000..661700fd3c
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+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.text.TextUtils;
+import android.util.Config;
+
+/**
+ * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox
+ * preference. It is turns on/off Bluetooth and ensures the summary of the
+ * preference reflects the current state.
+ */
+public class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
+
+ private static final boolean LOCAL_LOGD = Config.LOGD || false;
+ private static final String TAG = "BluetoothEnabler";
+
+ private final Context mContext;
+ private final CheckBoxPreference mCheckBoxPreference;
+ private final CharSequence mOriginalSummary;
+
+ private final LocalBluetoothManager mLocalManager;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleStateChanged(mLocalManager.getBluetoothState());
+ }
+ };
+
+ public BluetoothEnabler(Context context, CheckBoxPreference checkBoxPreference) {
+ mContext = context;
+ mCheckBoxPreference = checkBoxPreference;
+
+ mOriginalSummary = checkBoxPreference.getSummary();
+ checkBoxPreference.setPersistent(false);
+
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+ if (mLocalManager == null) {
+ // Bluetooth not supported
+ checkBoxPreference.setEnabled(false);
+ }
+ }
+
+ public void resume() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ ExtendedBluetoothState state = mLocalManager.getBluetoothState();
+ // This is the widget enabled state, not the preference toggled state
+ mCheckBoxPreference.setEnabled(state == ExtendedBluetoothState.ENABLED ||
+ state == ExtendedBluetoothState.DISABLED);
+ // BT state is not a sticky broadcast, so set it manually
+ handleStateChanged(state);
+
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(LocalBluetoothManager.EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+ mCheckBoxPreference.setOnPreferenceChangeListener(this);
+ }
+
+ public void pause() {
+ if (mLocalManager == null) {
+ return;
+ }
+
+ mContext.unregisterReceiver(mReceiver);
+ mCheckBoxPreference.setOnPreferenceChangeListener(null);
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ // Turn on/off BT
+ setEnabled((Boolean) value);
+
+ // Don't update UI to opposite state until we're sure
+ return false;
+ }
+
+ private void setEnabled(final boolean enable) {
+ // Disable preference
+ mCheckBoxPreference.setEnabled(false);
+
+ mLocalManager.setBluetoothEnabled(enable);
+ }
+
+ private void handleStateChanged(ExtendedBluetoothState state) {
+
+ if (state == ExtendedBluetoothState.DISABLED || state == ExtendedBluetoothState.ENABLED) {
+ mCheckBoxPreference.setChecked(state == ExtendedBluetoothState.ENABLED);
+ mCheckBoxPreference
+ .setSummary(state == ExtendedBluetoothState.DISABLED ? mOriginalSummary : null);
+
+ mCheckBoxPreference.setEnabled(isEnabledByDependency());
+
+ } else if (state == ExtendedBluetoothState.ENABLING ||
+ state == ExtendedBluetoothState.DISABLING) {
+ mCheckBoxPreference.setSummary(state == ExtendedBluetoothState.ENABLING
+ ? R.string.wifi_starting
+ : R.string.wifi_stopping);
+
+ } else if (state == ExtendedBluetoothState.UNKNOWN) {
+ mCheckBoxPreference.setChecked(false);
+ mCheckBoxPreference.setSummary(R.string.wifi_error);
+ mCheckBoxPreference.setEnabled(true);
+ }
+ }
+
+ private boolean isEnabledByDependency() {
+ Preference dep = getDependencyPreference();
+ if (dep == null) {
+ return true;
+ }
+
+ return !dep.shouldDisableDependents();
+ }
+
+ private Preference getDependencyPreference() {
+ String depKey = mCheckBoxPreference.getDependency();
+ if (TextUtils.isEmpty(depKey)) {
+ return null;
+ }
+
+ return mCheckBoxPreference.getPreferenceManager().findPreference(depKey);
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
new file mode 100644
index 0000000000..bcad20613b
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+/**
+ * BluetoothEventRedirector receives broadcasts and callbacks from the Bluetooth
+ * API and dispatches the event on the UI thread to the right class in the
+ * Settings.
+ */
+public class BluetoothEventRedirector {
+ private static final String TAG = "BluetoothEventRedirector";
+ private static final boolean V = LocalBluetoothManager.V;
+
+ private LocalBluetoothManager mManager;
+ private Handler mUiHandler = new Handler();
+
+ private IBluetoothDeviceCallback mBtDevCallback = new IBluetoothDeviceCallback.Stub() {
+ public void onCreateBondingResult(final String address, final int result) {
+ if (V) {
+ Log.v(TAG, "onCreateBondingResult(" + address + ", " + result + ")");
+ }
+
+ mUiHandler.post(new Runnable() {
+ public void run() {
+ boolean wasSuccess = result == BluetoothDevice.RESULT_SUCCESS;
+ LocalBluetoothDeviceManager deviceManager = mManager.getLocalDeviceManager();
+ deviceManager.onBondingStateChanged(address, wasSuccess);
+ if (!wasSuccess) {
+ deviceManager.onBondingError(address);
+ }
+ }
+ });
+ }
+
+ public void onEnableResult(int result) { }
+ public void onGetRemoteServiceChannelResult(String address, int channel) { }
+ };
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (V) {
+ Log.v(TAG, "Received " + intent.getAction());
+ }
+
+ String action = intent.getAction();
+ String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+
+ if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
+ mManager.setBluetoothStateInt(ExtendedBluetoothState.ENABLED);
+ } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
+ mManager.setBluetoothStateInt(ExtendedBluetoothState.DISABLED);
+
+ } else if (action.equals(BluetoothIntent.DISCOVERY_STARTED_ACTION)) {
+ mManager.onScanningStateChanged(true);
+ } else if (action.equals(BluetoothIntent.DISCOVERY_COMPLETED_ACTION)) {
+ mManager.onScanningStateChanged(false);
+
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)) {
+ short rssi = intent.getShortExtra(BluetoothIntent.RSSI, Short.MIN_VALUE);
+ mManager.getLocalDeviceManager().onDeviceAppeared(address, rssi);
+ } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION)) {
+ mManager.getLocalDeviceManager().onDeviceDisappeared(address);
+ } else if (action.equals(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION)) {
+ mManager.getLocalDeviceManager().onDeviceNameUpdated(address);
+
+ } else if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) {
+ mManager.getLocalDeviceManager().onBondingStateChanged(address, true);
+ } else if (action.equals(BluetoothIntent.BONDING_REMOVED_ACTION)) {
+ mManager.getLocalDeviceManager().onBondingStateChanged(address, false);
+
+ } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
+ mManager.getLocalDeviceManager().onProfileStateChanged(address);
+
+ int newState = intent.getIntExtra(BluetoothIntent.HEADSET_STATE, 0);
+ int oldState = intent.getIntExtra(BluetoothIntent.HEADSET_PREVIOUS_STATE, 0);
+ if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
+ oldState == BluetoothHeadset.STATE_CONNECTING) {
+ mManager.getLocalDeviceManager().onConnectingError(address);
+ }
+
+ } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) {
+ mManager.getLocalDeviceManager().onProfileStateChanged(address);
+
+ int newState = intent.getIntExtra(BluetoothA2dp.SINK_STATE, 0);
+ int oldState = intent.getIntExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, 0);
+ if (newState == BluetoothA2dp.STATE_DISCONNECTED &&
+ oldState == BluetoothA2dp.STATE_CONNECTING) {
+ mManager.getLocalDeviceManager().onConnectingError(address);
+ }
+ }
+ }
+ };
+
+ public BluetoothEventRedirector(LocalBluetoothManager localBluetoothManager) {
+ mManager = localBluetoothManager;
+ }
+
+ public void start() {
+ IntentFilter filter = new IntentFilter();
+
+ // Bluetooth on/off broadcasts
+ filter.addAction(BluetoothIntent.ENABLED_ACTION);
+ filter.addAction(BluetoothIntent.DISABLED_ACTION);
+
+ // Discovery broadcasts
+ filter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION);
+ filter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
+ filter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
+ filter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
+ filter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
+
+ // Pairing broadcasts
+ filter.addAction(BluetoothIntent.BONDING_CREATED_ACTION);
+ filter.addAction(BluetoothIntent.BONDING_REMOVED_ACTION);
+
+ // Fine-grained state broadcasts
+ filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+ filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
+
+ mManager.getContext().registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ public void stop() {
+ mManager.getContext().unregisterReceiver(mBroadcastReceiver);
+ }
+
+ public IBluetoothDeviceCallback getBluetoothDeviceCallback() {
+ return mBtDevCallback;
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
new file mode 100644
index 0000000000..3065b26c5e
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.preference.EditTextPreference;
+import android.preference.PreferenceManager;
+import android.util.AttributeSet;
+
+/**
+ * BluetoothNamePreference is the preference type for editing the device's
+ * Bluetooth name. It asks the user for a name, and persists it via the
+ * Bluetooth API.
+ */
+public class BluetoothNamePreference extends EditTextPreference {
+ private static final String TAG = "BluetoothNamePreference";
+
+ private LocalBluetoothManager mLocalManager;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ setSummaryToName();
+ }
+ };
+
+ public BluetoothNamePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+
+ setSummaryToName();
+ }
+
+ public void resume() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothIntent.ENABLED_ACTION);
+ filter.addAction(BluetoothIntent.NAME_CHANGED_ACTION);
+ getContext().registerReceiver(mReceiver, filter);
+ }
+
+ public void pause() {
+ getContext().unregisterReceiver(mReceiver);
+ }
+
+ private void setSummaryToName() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+ if (manager.isEnabled()) {
+ setSummary(manager.getName());
+ }
+ }
+
+ @Override
+ protected boolean persistString(String value) {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+ manager.setName(value);
+ return true;
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPinDialog.java b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
new file mode 100644
index 0000000000..291d0c15ca
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.settings.R;
+
+/**
+ * BluetoothPinDialog asks the user to enter a PIN for pairing with a remote
+ * Bluetooth device. It is an activity that appears as a dialog.
+ */
+public class BluetoothPinDialog extends AlertActivity implements DialogInterface.OnClickListener {
+ private static final String TAG = "BluetoothPinDialog";
+
+ private LocalBluetoothManager mLocalManager;
+ private String mAddress;
+ private EditText mPinView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (!intent.getAction().equals(BluetoothIntent.PAIRING_REQUEST_ACTION))
+ {
+ Log.e(TAG,
+ "Error: this activity may be started only with intent " +
+ BluetoothIntent.PAIRING_REQUEST_ACTION);
+ finish();
+ }
+
+ mLocalManager = LocalBluetoothManager.getInstance(this);
+ mAddress = intent.getStringExtra(BluetoothIntent.ADDRESS);
+
+ // Set up the "dialog"
+ final AlertController.AlertParams p = mAlertParams;
+ p.mIconId = android.R.drawable.ic_dialog_info;
+ p.mTitle = getString(R.string.bluetooth_pin_entry);
+ p.mView = createView();
+ p.mPositiveButtonText = getString(android.R.string.ok);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(android.R.string.cancel);
+ p.mNegativeButtonListener = this;
+ setupAlert();
+ }
+
+ private View createView() {
+ View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
+
+ String name = mLocalManager.getLocalDeviceManager().getName(mAddress);
+ TextView messageView = (TextView) view.findViewById(R.id.message);
+ messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name));
+
+ mPinView = (EditText) view.findViewById(R.id.text);
+
+ return view;
+ }
+
+ private void onPair(String pin) {
+ byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
+
+ if (pinBytes == null) {
+ return;
+ }
+
+ mLocalManager.getBluetoothManager().setPin(mAddress, pinBytes);
+ }
+
+ private void onCancel() {
+ mLocalManager.getBluetoothManager().cancelPin(mAddress);
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ onPair(mPinView.getText().toString());
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ onCancel();
+ break;
+ }
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPinRequest.java b/src/com/android/settings/bluetooth/BluetoothPinRequest.java
new file mode 100644
index 0000000000..619052d87f
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinRequest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+/**
+ * BluetoothPinRequest is a receiver for any Bluetooth pairing PIN request. It
+ * checks if the Bluetooth Settings is currently visible and brings up the PIN
+ * entry dialog. Otherwise it puts a Notification in the status bar, which can
+ * be clicked to bring up the PIN entry dialog.
+ */
+public class BluetoothPinRequest extends BroadcastReceiver {
+
+ 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 (action.equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
+
+ LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(context);
+
+ String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+ Intent pinIntent = new Intent();
+ pinIntent.setClass(context, BluetoothPinDialog.class);
+ pinIntent.putExtra(BluetoothIntent.ADDRESS, address);
+ pinIntent.setAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
+ pinIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (localManager.getForegroundActivity() != null) {
+ // Since the BT-related activity is in the foreground, just open the dialog
+ context.startActivity(pinIntent);
+
+ } else {
+
+ // Put up a notification that leads to the dialog
+ Resources res = context.getResources();
+ Notification notification = new Notification(
+ android.R.drawable.stat_sys_data_bluetooth,
+ res.getString(R.string.bluetooth_notif_ticker),
+ System.currentTimeMillis());
+
+ PendingIntent pending = PendingIntent.getActivity(context, 0,
+ pinIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ String name = intent.getStringExtra(BluetoothIntent.NAME);
+ if (TextUtils.isEmpty(name)) {
+ name = localManager.getLocalDeviceManager().getName(address);
+ }
+
+ notification.setLatestEventInfo(context,
+ res.getString(R.string.bluetooth_notif_title),
+ res.getString(R.string.bluetooth_notif_message) + name,
+ pending);
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+
+ NotificationManager manager = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.notify(NOTIFICATION_ID, notification);
+ }
+
+ } else if (action.equals(BluetoothIntent.PAIRING_CANCEL_ACTION)) {
+
+ // 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
new file mode 100644
index 0000000000..316e831ac6
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.ProgressCategory;
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import java.util.List;
+import java.util.WeakHashMap;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+/**
+ * BluetoothSettings is the Settings screen for Bluetooth configuration and
+ * connection management.
+ */
+public class BluetoothSettings extends PreferenceActivity
+ implements LocalBluetoothManager.Callback {
+
+ private static final String TAG = "BluetoothSettings";
+
+ private static final int MENU_SCAN = Menu.FIRST;
+
+ private static final String KEY_BT_CHECKBOX = "bt_checkbox";
+ private static final String KEY_BT_DISCOVERABLE = "bt_discoverable";
+ private static final String KEY_BT_DEVICE_LIST = "bt_device_list";
+ private static final String KEY_BT_NAME = "bt_name";
+ private static final String KEY_BT_SCAN = "bt_scan";
+
+ private LocalBluetoothManager mLocalManager;
+
+ private BluetoothEnabler mEnabler;
+ private BluetoothDiscoverableEnabler mDiscoverableEnabler;
+
+ private BluetoothNamePreference mNamePreference;
+
+ private ProgressCategory mDeviceList;
+
+ private WeakHashMap<LocalBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
+ new WeakHashMap<LocalBluetoothDevice, BluetoothDevicePreference>();
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // TODO: put this in callback instead of receiving
+ onBluetoothStateChanged(mLocalManager.getBluetoothState());
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mLocalManager = LocalBluetoothManager.getInstance(this);
+ if (mLocalManager == null) finish();
+
+ addPreferencesFromResource(R.xml.bluetooth_settings);
+
+ mEnabler = new BluetoothEnabler(
+ this,
+ (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX));
+
+ mDiscoverableEnabler = new BluetoothDiscoverableEnabler(
+ this,
+ (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE));
+
+ mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
+
+ mDeviceList = (ProgressCategory) findPreference(KEY_BT_DEVICE_LIST);
+
+ registerForContextMenu(getListView());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // Repopulate (which isn't too bad since it's cached in the settings
+ // bluetooth manager
+ mDevicePreferenceMap.clear();
+ mDeviceList.removeAll();
+ addDevices();
+
+ mEnabler.resume();
+ mDiscoverableEnabler.resume();
+ mNamePreference.resume();
+ mLocalManager.registerCallback(this);
+
+ mLocalManager.startScanning(false);
+
+ registerReceiver(mReceiver,
+ new IntentFilter(LocalBluetoothManager.EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+
+ mLocalManager.setForegroundActivity(this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ mLocalManager.setForegroundActivity(null);
+
+ unregisterReceiver(mReceiver);
+
+ mLocalManager.unregisterCallback(this);
+ mNamePreference.pause();
+ mDiscoverableEnabler.pause();
+ mEnabler.pause();
+ }
+
+ private void addDevices() {
+ List<LocalBluetoothDevice> devices = mLocalManager.getLocalDeviceManager().getDevicesCopy();
+ for (LocalBluetoothDevice device : devices) {
+ onDeviceAdded(device);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_SCAN, 0, R.string.bluetooth_scan_for_devices)
+ .setIcon(R.drawable.ic_menu_refresh)
+ .setAlphabeticShortcut('r');
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(MENU_SCAN).setEnabled(mLocalManager.getBluetoothManager().isEnabled());
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case MENU_SCAN:
+ mLocalManager.startScanning(true);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+
+ if (KEY_BT_SCAN.equals(preference.getKey())) {
+ mLocalManager.startScanning(true);
+ return true;
+ }
+
+ if (preference instanceof BluetoothDevicePreference) {
+ BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
+ btPreference.getDevice().onClicked();
+ return true;
+ }
+
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ LocalBluetoothDevice device = getDeviceFromMenuInfo(menuInfo);
+ if (device == null) return;
+
+ device.onCreateContextMenu(menu);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ LocalBluetoothDevice device = getDeviceFromMenuInfo(item.getMenuInfo());
+ if (device == null) return false;
+
+ device.onContextItemSelected(item);
+ return true;
+ }
+
+ private LocalBluetoothDevice getDeviceFromMenuInfo(ContextMenuInfo menuInfo) {
+ if ((menuInfo == null) || !(menuInfo instanceof AdapterContextMenuInfo)) {
+ return null;
+ }
+
+ AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
+ Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(
+ adapterMenuInfo.position);
+ if (pref == null || !(pref instanceof BluetoothDevicePreference)) {
+ return null;
+ }
+
+ return ((BluetoothDevicePreference) pref).getDevice();
+ }
+
+ public void onDeviceAdded(LocalBluetoothDevice device) {
+
+ if (mDevicePreferenceMap.get(device) != null) {
+ throw new IllegalStateException("Got onDeviceAdded, but device already exists");
+ }
+
+ createDevicePreference(device);
+ }
+
+ private void createDevicePreference(LocalBluetoothDevice device) {
+ BluetoothDevicePreference preference = new BluetoothDevicePreference(this, device);
+ mDeviceList.addPreference(preference);
+ mDevicePreferenceMap.put(device, preference);
+ }
+
+ public void onDeviceDeleted(LocalBluetoothDevice device) {
+ BluetoothDevicePreference preference = mDevicePreferenceMap.remove(device);
+ if (preference != null) {
+ mDeviceList.removePreference(preference);
+ }
+ }
+
+ public void onScanningStateChanged(boolean started) {
+ mDeviceList.setProgress(started);
+ }
+
+ private void onBluetoothStateChanged(ExtendedBluetoothState bluetoothState) {
+ // When bluetooth is enabled (and we are in the activity, which we are),
+ // we should start a scan
+ if (bluetoothState == ExtendedBluetoothState.ENABLED) {
+ mLocalManager.startScanning(false);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
new file mode 100644
index 0000000000..f29ec7950b
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.util.Log;
+
+/**
+ * ConnectSpecificProfilesActivity presents the user with all of the profiles
+ * for a particular device, and allows him to choose which should be connected
+ * (or disconnected).
+ */
+public class ConnectSpecificProfilesActivity extends PreferenceActivity
+ implements LocalBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "ConnectSpecificProfilesActivity";
+
+ private static final String KEY_ONLINE_MODE = "online_mode";
+ private static final String KEY_TITLE = "title";
+ private static final String KEY_PROFILE_CONTAINER = "profile_container";
+
+ public static final String EXTRA_ADDRESS = "address";
+
+ private LocalBluetoothManager mManager;
+ private LocalBluetoothDevice mDevice;
+
+ private PreferenceGroup mProfileContainer;
+ private CheckBoxPreference mOnlineModePreference;
+
+ /**
+ * The current mode of this activity and its checkboxes (either online mode
+ * or offline mode). In online mode, user interactions with the profile
+ * checkboxes will also toggle the profile's connectivity. In offline mode,
+ * they will not, and only the preferred state will be saved for the
+ * profile.
+ */
+ private boolean mOnlineMode;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String address;
+ if (savedInstanceState != null) {
+ address = savedInstanceState.getString(EXTRA_ADDRESS);
+ } else {
+ Intent intent = getIntent();
+ address = intent.getStringExtra(EXTRA_ADDRESS);
+ }
+
+ if (TextUtils.isEmpty(address)) {
+ Log.w(TAG, "Activity started without address");
+ finish();
+ }
+
+ mManager = LocalBluetoothManager.getInstance(this);
+ mDevice = mManager.getLocalDeviceManager().findDevice(address);
+ if (mDevice == null) {
+ Log.w(TAG, "Device not found, cannot connect to it");
+ finish();
+ }
+
+ addPreferencesFromResource(R.xml.bluetooth_device_advanced);
+ mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
+
+ // Set the title of the screen
+ findPreference(KEY_TITLE).setTitle(
+ getString(R.string.bluetooth_device_advanced_title, mDevice.getName()));
+
+ // Listen for check/uncheck of the online mode checkbox
+ mOnlineModePreference = (CheckBoxPreference) findPreference(KEY_ONLINE_MODE);
+ mOnlineModePreference.setOnPreferenceChangeListener(this);
+
+ // Add a preference for each profile
+ addPreferencesForProfiles();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putString(EXTRA_ADDRESS, mDevice.getAddress());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ mManager.setForegroundActivity(this);
+ mDevice.registerCallback(this);
+
+ refresh(true);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ mDevice.unregisterCallback(this);
+ mManager.setForegroundActivity(null);
+ }
+
+ private void addPreferencesForProfiles() {
+ for (Profile profile : mDevice.getProfiles()) {
+ Preference pref = createProfilePreference(profile);
+ mProfileContainer.addPreference(pref);
+ }
+ }
+
+ /**
+ * Creates a checkbox preference for the particular profile. The key will be
+ * the profile's name.
+ *
+ * @param profile The profile for which the preference controls.
+ * @return A preference that allows the user to choose whether this profile
+ * will be connected to.
+ */
+ private CheckBoxPreference createProfilePreference(Profile profile) {
+ CheckBoxPreference pref = new CheckBoxPreference(this);
+ pref.setKey(profile.toString());
+ pref.setTitle(profile.localizedString);
+ pref.setPersistent(false);
+ pref.setOnPreferenceChangeListener(this);
+
+ refreshProfilePreference(pref, profile);
+
+ return pref;
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ String key = preference.getKey();
+ if (TextUtils.isEmpty(key) || newValue == null) return true;
+
+ if (key.equals(KEY_ONLINE_MODE)) {
+ onOnlineModeCheckedStateChanged((Boolean) newValue);
+
+ } else {
+ Profile profile = getProfileOf(preference);
+ if (profile == null) return false;
+ onProfileCheckedStateChanged(profile, (Boolean) newValue);
+ }
+
+ return true;
+ }
+
+ private void onOnlineModeCheckedStateChanged(boolean checked) {
+ switchModes(checked, false);
+ }
+
+ private void onProfileCheckedStateChanged(Profile profile, boolean checked) {
+ if (mOnlineMode) {
+ if (checked) {
+ mDevice.connect(profile);
+ } else {
+ mDevice.disconnect(profile);
+ }
+ }
+
+ LocalBluetoothProfileManager.setPreferredProfile(this, mDevice.getAddress(), profile,
+ checked);
+ }
+
+ public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+ refresh(false);
+ }
+
+ private void refresh(boolean forceRefresh) {
+ // The online mode could have changed
+ updateOnlineMode(forceRefresh);
+ refreshProfiles();
+ refreshOnlineModePreference();
+ }
+
+ private void updateOnlineMode(boolean force) {
+ // Connected or Connecting (and Disconnecting, which is fine)
+ boolean onlineMode = mDevice.isConnected() || mDevice.isBusy();
+ switchModes(onlineMode, force);
+ }
+
+ /**
+ * Switches between online/offline mode.
+ *
+ * @param onlineMode Whether to be in online mode, or offline mode.
+ */
+ private void switchModes(boolean onlineMode, boolean force) {
+ if (mOnlineMode != onlineMode || force) {
+ mOnlineMode = onlineMode;
+
+ if (onlineMode) {
+ mDevice.connect();
+ } else {
+ mDevice.disconnect();
+ }
+
+ refreshOnlineModePreference();
+ }
+ }
+
+ private void refreshOnlineModePreference() {
+ mOnlineModePreference.setChecked(mOnlineMode);
+
+ /**
+ * If the device is online, show status. Otherwise, show a summary that
+ * describes what the checkbox does.
+ */
+ mOnlineModePreference.setSummary(mOnlineMode ? mDevice.getSummary()
+ : R.string.bluetooth_device_advanced_online_mode_summary);
+ }
+
+ private void refreshProfiles() {
+ for (Profile profile : mDevice.getProfiles()) {
+ CheckBoxPreference profilePref =
+ (CheckBoxPreference) findPreference(profile.toString());
+ if (profilePref == null) continue;
+
+ refreshProfilePreference(profilePref, profile);
+ }
+ }
+
+ private void refreshProfilePreference(CheckBoxPreference profilePref, Profile profile) {
+ String address = mDevice.getAddress();
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mManager, profile);
+
+ int connectionStatus = profileManager.getConnectionStatus(address);
+
+ profilePref.setSummary(getProfileSummary(profileManager, profile, address,
+ connectionStatus, mOnlineMode));
+
+ profilePref.setChecked(
+ LocalBluetoothProfileManager.isPreferredProfile(this, address, profile));
+ }
+
+ private Profile getProfileOf(Preference pref) {
+ if (!(pref instanceof CheckBoxPreference)) return null;
+ String key = pref.getKey();
+ if (TextUtils.isEmpty(key)) return null;
+
+ try {
+ return Profile.valueOf(pref.getKey());
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private static int getProfileSummary(LocalBluetoothProfileManager profileManager,
+ Profile profile, String address, int connectionStatus, boolean onlineMode) {
+ if (!onlineMode || connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) {
+ return getProfileSummaryForSettingPreference(profile);
+ } else {
+ return profileManager.getSummary(address);
+ }
+ }
+
+ /**
+ * Gets the summary that describes when checked, it will become a preferred profile.
+ *
+ * @param profile The profile to get the summary for.
+ * @return The summary.
+ */
+ private static final int getProfileSummaryForSettingPreference(Profile profile) {
+ switch (profile) {
+ case A2DP:
+ return R.string.bluetooth_a2dp_profile_summary_use_for;
+ case HEADSET:
+ return R.string.bluetooth_headset_profile_summary_use_for;
+ default:
+ return 0;
+ }
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothDevice.java b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
new file mode 100644
index 0000000000..a8f79ffca1
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LocalBluetoothDevice represents a remote Bluetooth device. It contains
+ * attributes of the device (such as the address, name, RSSI, etc.) and
+ * functionality that can be performed on the device (connect, pair, disconnect,
+ * etc.).
+ */
+public class LocalBluetoothDevice implements Comparable<LocalBluetoothDevice> {
+ private static final String TAG = "LocalBluetoothDevice";
+
+ private static final int CONTEXT_ITEM_CONNECT = Menu.FIRST + 1;
+ private static final int CONTEXT_ITEM_DISCONNECT = Menu.FIRST + 2;
+ private static final int CONTEXT_ITEM_UNPAIR = Menu.FIRST + 3;
+ private static final int CONTEXT_ITEM_CONNECT_ADVANCED = Menu.FIRST + 4;
+
+ private final String mAddress;
+ private String mName;
+ private short mRssi;
+ private int mBtClass = BluetoothClass.ERROR;
+
+ private List<Profile> mProfiles = new ArrayList<Profile>();
+
+ private boolean mVisible;
+
+ private int mPairingStatus;
+
+ private final LocalBluetoothManager mLocalManager;
+
+ private List<Callback> mCallbacks = new ArrayList<Callback>();
+
+ /**
+ * When we connect to multiple profiles, we only want to display a single
+ * error even if they all fail. This tracks that state.
+ */
+ private boolean mIsConnectingErrorPossible;
+
+ LocalBluetoothDevice(Context context, String address) {
+ mLocalManager = LocalBluetoothManager.getInstance(context);
+ if (mLocalManager == null) {
+ throw new IllegalStateException(
+ "Cannot use LocalBluetoothDevice without Bluetooth hardware");
+ }
+
+ mAddress = address;
+
+ fillData();
+ }
+
+ public void onClicked() {
+ int pairingStatus = getPairingStatus();
+
+ if (isConnected()) {
+ askDisconnect();
+ } else if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED) {
+ connect();
+ } else if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_UNPAIRED) {
+ pair();
+ }
+ }
+
+ public void disconnect() {
+ for (Profile profile : mProfiles) {
+ disconnect(profile);
+ }
+ }
+
+ public void disconnect(Profile profile) {
+ LocalBluetoothProfileManager profileManager =
+ LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+ int status = profileManager.getConnectionStatus(mAddress);
+ if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+ profileManager.disconnect(mAddress);
+ }
+ }
+
+ public void askDisconnect() {
+ Context context = mLocalManager.getForegroundActivity();
+ if (context == null) {
+ // Cannot ask, since we need an activity context
+ disconnect();
+ return;
+ }
+
+ Resources res = context.getResources();
+
+ String name = getName();
+ if (TextUtils.isEmpty(name)) {
+ name = res.getString(R.string.bluetooth_device);
+ }
+ String message = res.getString(R.string.bluetooth_disconnect_blank, name);
+
+ DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ disconnect();
+ }
+ };
+
+ AlertDialog ad = new AlertDialog.Builder(context)
+ .setTitle(getName())
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, disconnectListener)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+
+ public void connect() {
+ if (!ensurePaired()) return;
+
+ Context context = mLocalManager.getContext();
+ boolean hasAtLeastOnePreferredProfile = false;
+ for (Profile profile : mProfiles) {
+ if (LocalBluetoothProfileManager.isPreferredProfile(context, mAddress, profile)) {
+ hasAtLeastOnePreferredProfile = true;
+ connect(profile);
+ }
+ }
+
+ if (!hasAtLeastOnePreferredProfile) {
+ connectAndPreferAllProfiles();
+ }
+ }
+
+ private void connectAndPreferAllProfiles() {
+ if (!ensurePaired()) return;
+
+ Context context = mLocalManager.getContext();
+ for (Profile profile : mProfiles) {
+ LocalBluetoothProfileManager.setPreferredProfile(context, mAddress, profile, true);
+ connect(profile);
+ }
+ }
+
+ public void connect(Profile profile) {
+ if (!ensurePaired()) return;
+
+ LocalBluetoothProfileManager profileManager =
+ LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+ int status = profileManager.getConnectionStatus(mAddress);
+ if (!SettingsBtStatus.isConnectionStatusConnected(status)) {
+ mIsConnectingErrorPossible = true;
+ if (profileManager.connect(mAddress) != BluetoothDevice.RESULT_SUCCESS) {
+ showConnectingError();
+ }
+ }
+ }
+
+ public void showConnectingError() {
+ if (!mIsConnectingErrorPossible) return;
+ mIsConnectingErrorPossible = false;
+
+ mLocalManager.showError(mAddress, R.string.bluetooth_error_title,
+ R.string.bluetooth_connecting_error_message);
+ }
+
+ private boolean ensurePaired() {
+ if (getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_UNPAIRED) {
+ pair();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void pair() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ // Pairing doesn't work if scanning, so cancel
+ if (manager.isDiscovering()) {
+ manager.cancelDiscovery();
+ }
+
+ if (mLocalManager.createBonding(mAddress)) {
+ setPairingStatus(SettingsBtStatus.PAIRING_STATUS_PAIRING);
+ }
+ }
+
+ public void unpair() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ switch (getPairingStatus()) {
+ case SettingsBtStatus.PAIRING_STATUS_PAIRED:
+ manager.removeBonding(mAddress);
+ break;
+
+ case SettingsBtStatus.PAIRING_STATUS_PAIRING:
+ manager.cancelBondingProcess(mAddress);
+ break;
+ }
+ }
+
+ private void fillData() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+ fetchName();
+ mBtClass = manager.getRemoteClass(mAddress);
+
+ LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
+
+ mPairingStatus = manager.hasBonding(mAddress)
+ ? SettingsBtStatus.PAIRING_STATUS_PAIRED
+ : SettingsBtStatus.PAIRING_STATUS_UNPAIRED;
+
+ mVisible = false;
+
+ dispatchAttributesChanged();
+ }
+
+ public String getAddress() {
+ return mAddress;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void refreshName() {
+ fetchName();
+ dispatchAttributesChanged();
+ }
+
+ private void fetchName() {
+ mName = mLocalManager.getBluetoothManager().getRemoteName(mAddress);
+
+ if (TextUtils.isEmpty(mName)) {
+ mName = mAddress;
+ }
+ }
+
+ public void refresh() {
+ dispatchAttributesChanged();
+ }
+
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ void setVisible(boolean visible) {
+ if (mVisible != visible) {
+ mVisible = visible;
+ dispatchAttributesChanged();
+ }
+ }
+
+ public int getPairingStatus() {
+ return mPairingStatus;
+ }
+
+ void setPairingStatus(int pairingStatus) {
+ if (mPairingStatus != pairingStatus) {
+ mPairingStatus = pairingStatus;
+ dispatchAttributesChanged();
+ }
+ }
+
+ void setRssi(short rssi) {
+ if (mRssi != rssi) {
+ mRssi = rssi;
+ dispatchAttributesChanged();
+ }
+ }
+
+ /**
+ * Checks whether we are connected to this device (any profile counts).
+ *
+ * @return Whether it is connected.
+ */
+ public boolean isConnected() {
+ for (Profile profile : mProfiles) {
+ int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
+ .getConnectionStatus(mAddress);
+ if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isBusy() {
+ for (Profile profile : mProfiles) {
+ int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
+ .getConnectionStatus(mAddress);
+ if (SettingsBtStatus.isConnectionStatusBusy(status)) {
+ return true;
+ }
+ }
+
+ if (getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_PAIRING) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public int getBtClassDrawable() {
+
+ // First try looking at profiles
+ if (mProfiles.contains(Profile.A2DP)) {
+ return R.drawable.ic_bt_headphones_a2dp;
+ } else if (mProfiles.contains(Profile.HEADSET)) {
+ return R.drawable.ic_bt_headset_hfp;
+ }
+
+ // Fallback on class
+ switch (BluetoothClass.Device.Major.getDeviceMajor(mBtClass)) {
+ case BluetoothClass.Device.Major.COMPUTER:
+ return R.drawable.ic_bt_laptop;
+
+ case BluetoothClass.Device.Major.PHONE:
+ return R.drawable.ic_bt_cellphone;
+
+ default:
+ return 0;
+ }
+ }
+
+ public int getSummary() {
+ // TODO: clean up
+ int oneOffSummary = getOneOffSummary();
+ if (oneOffSummary != 0) {
+ return oneOffSummary;
+ }
+
+ for (Profile profile : mProfiles) {
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mLocalManager, profile);
+ int connectionStatus = profileManager.getConnectionStatus(mAddress);
+
+ if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) ||
+ connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING ||
+ connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) {
+ return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+ }
+ }
+
+ int pairingStatus = getPairingStatus();
+ return SettingsBtStatus.getPairingStatusSummary(pairingStatus);
+ }
+
+ /**
+ * We have special summaries when particular profiles are connected. This
+ * checks for those states and returns an applicable summary.
+ *
+ * @return A one-off summary that is applicable for the current state, or 0.
+ */
+ private int getOneOffSummary() {
+ boolean isA2dpConnected = false, isHeadsetConnected = false, isConnecting = false;
+
+ if (mProfiles.contains(Profile.A2DP)) {
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mLocalManager, Profile.A2DP);
+ isConnecting = profileManager.getConnectionStatus(mAddress) ==
+ SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ isA2dpConnected = profileManager.isConnected(mAddress);
+ }
+
+ if (mProfiles.contains(Profile.HEADSET)) {
+ LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+ .getProfileManager(mLocalManager, Profile.HEADSET);
+ isConnecting |= profileManager.getConnectionStatus(mAddress) ==
+ SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ isHeadsetConnected = profileManager.isConnected(mAddress);
+ }
+
+ if (isConnecting) {
+ // If any of these important profiles is connecting, prefer that
+ return SettingsBtStatus.getConnectionStatusSummary(
+ SettingsBtStatus.CONNECTION_STATUS_CONNECTING);
+ } else if (isA2dpConnected && isHeadsetConnected) {
+ return R.string.bluetooth_summary_connected_to_a2dp_headset;
+ } else if (isA2dpConnected) {
+ return R.string.bluetooth_summary_connected_to_a2dp;
+ } else if (isHeadsetConnected) {
+ return R.string.bluetooth_summary_connected_to_headset;
+ } else {
+ return 0;
+ }
+ }
+
+ public List<Profile> getProfiles() {
+ return new ArrayList<Profile>(mProfiles);
+ }
+
+ public void onCreateContextMenu(ContextMenu menu) {
+ // No context menu if it is busy (none of these items are applicable if busy)
+ if (isBusy()) return;
+
+ // No context menu if there are no profiles
+ if (mProfiles.size() == 0) return;
+
+ int pairingStatus = getPairingStatus();
+ boolean isConnected = isConnected();
+
+ menu.setHeaderTitle(getName());
+
+ if (isConnected) {
+ menu.add(0, CONTEXT_ITEM_DISCONNECT, 0, R.string.bluetooth_device_context_disconnect);
+ } else {
+ // For connection action, show either "Connect" or "Pair & connect"
+ int connectString = pairingStatus == SettingsBtStatus.PAIRING_STATUS_UNPAIRED
+ ? R.string.bluetooth_device_context_pair_connect
+ : R.string.bluetooth_device_context_connect;
+ menu.add(0, CONTEXT_ITEM_CONNECT, 0, connectString);
+ }
+
+ if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED) {
+ // For unpair action, show either "Unpair" or "Disconnect & unpair"
+ int unpairString = isConnected
+ ? R.string.bluetooth_device_context_disconnect_unpair
+ : R.string.bluetooth_device_context_unpair;
+ menu.add(0, CONTEXT_ITEM_UNPAIR, 0, unpairString);
+
+ // Show the connection options item
+ menu.add(0, CONTEXT_ITEM_CONNECT_ADVANCED, 0,
+ R.string.bluetooth_device_context_connect_advanced);
+ }
+ }
+
+ /**
+ * Called when a context menu item is clicked.
+ *
+ * @param item The item that was clicked.
+ */
+ public void onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case CONTEXT_ITEM_DISCONNECT:
+ disconnect();
+ break;
+
+ case CONTEXT_ITEM_CONNECT:
+ connect();
+ break;
+
+ case CONTEXT_ITEM_UNPAIR:
+ unpair();
+ break;
+
+ case CONTEXT_ITEM_CONNECT_ADVANCED:
+ Intent intent = new Intent();
+ // Need an activity context to open this in our task
+ Context context = mLocalManager.getForegroundActivity();
+ if (context == null) {
+ // Fallback on application context, and open in a new task
+ context = mLocalManager.getContext();
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+ intent.setClass(context, ConnectSpecificProfilesActivity.class);
+ intent.putExtra(ConnectSpecificProfilesActivity.EXTRA_ADDRESS, mAddress);
+ context.startActivity(intent);
+ break;
+ }
+ }
+
+ public void registerCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ private void dispatchAttributesChanged() {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceAttributesChanged(this);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mAddress;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ((o == null) || !(o instanceof LocalBluetoothDevice)) {
+ throw new ClassCastException();
+ }
+
+ return mAddress.equals(((LocalBluetoothDevice) o).mAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ return mAddress.hashCode();
+ }
+
+ public int compareTo(LocalBluetoothDevice another) {
+ int comparison;
+
+ // Connected above not connected
+ comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Paired above not paired
+ comparison = (another.mPairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED ? 1 : 0) -
+ (mPairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Visible above not visible
+ comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Stronger signal above weaker signal
+ comparison = another.mRssi - mRssi;
+ if (comparison != 0) return comparison;
+
+ // Fallback on name
+ return getName().compareTo(another.getName());
+ }
+
+ public interface Callback {
+ void onDeviceAttributesChanged(LocalBluetoothDevice device);
+ }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
new file mode 100644
index 0000000000..48a41f1a65
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+import android.widget.Toast;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.Callback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LocalBluetoothDeviceManager manages the set of remote Bluetooth devices.
+ */
+public class LocalBluetoothDeviceManager {
+ private static final String TAG = "LocalBluetoothDeviceManager";
+
+ final LocalBluetoothManager mLocalManager;
+ final List<Callback> mCallbacks;
+
+ final List<LocalBluetoothDevice> mDevices = new ArrayList<LocalBluetoothDevice>();
+
+ public LocalBluetoothDeviceManager(LocalBluetoothManager localManager) {
+ mLocalManager = localManager;
+ mCallbacks = localManager.getCallbacks();
+ readPairedDevices();
+ }
+
+ private synchronized void readPairedDevices() {
+ BluetoothDevice manager = mLocalManager.getBluetoothManager();
+ String[] bondedAddresses = manager.listBondings();
+ if (bondedAddresses == null) return;
+
+ for (String address : bondedAddresses) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+ mDevices.add(device);
+ dispatchDeviceAdded(device);
+ }
+ }
+ }
+
+ public synchronized List<LocalBluetoothDevice> getDevicesCopy() {
+ return new ArrayList<LocalBluetoothDevice>(mDevices);
+ }
+
+ void onBluetoothStateChanged(boolean enabled) {
+ if (enabled) {
+ readPairedDevices();
+ }
+ }
+
+ public synchronized void onDeviceAppeared(String address, short rssi) {
+ boolean deviceAdded = false;
+
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+ mDevices.add(device);
+ deviceAdded = true;
+ }
+
+ device.setRssi(rssi);
+ device.setVisible(true);
+
+ if (deviceAdded) {
+ dispatchDeviceAdded(device);
+ }
+ }
+
+ public synchronized void onDeviceDisappeared(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) return;
+
+ device.setVisible(false);
+ checkForDeviceRemoval(device);
+ }
+
+ private void checkForDeviceRemoval(LocalBluetoothDevice device) {
+ if (device.getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_UNPAIRED &&
+ !device.isVisible()) {
+ // If device isn't paired, remove it altogether
+ mDevices.remove(device);
+ dispatchDeviceDeleted(device);
+ }
+ }
+
+ public synchronized void onDeviceNameUpdated(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device != null) {
+ device.refreshName();
+ }
+ }
+
+ public synchronized LocalBluetoothDevice findDevice(String address) {
+
+ for (int i = mDevices.size() - 1; i >= 0; i--) {
+ LocalBluetoothDevice device = mDevices.get(i);
+
+ if (device.getAddress().equals(address)) {
+ return device;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to get the name of a remote device, otherwise returns the address.
+ *
+ * @param address The address.
+ * @return The name, or if unavailable, the address.
+ */
+ public String getName(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ return device != null ? device.getName() : address;
+ }
+
+ private void dispatchDeviceAdded(LocalBluetoothDevice device) {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceAdded(device);
+ }
+ }
+
+ // TODO: divider between prev paired/connected and scanned
+ }
+
+ private void dispatchDeviceDeleted(LocalBluetoothDevice device) {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceDeleted(device);
+ }
+ }
+ }
+
+ public synchronized void onBondingStateChanged(String address, boolean created) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) {
+ Log.e(TAG, "Got bonding state changed for " + address +
+ ", but we have no record of that device.");
+ return;
+ }
+
+ device.setPairingStatus(created ? SettingsBtStatus.PAIRING_STATUS_PAIRED
+ : SettingsBtStatus.PAIRING_STATUS_UNPAIRED);
+ checkForDeviceRemoval(device);
+
+ if (created) {
+ // Auto-connect after pairing
+ device.connect();
+ }
+ }
+
+ public synchronized void onBondingError(String address) {
+ mLocalManager.showError(address, R.string.bluetooth_error_title,
+ R.string.bluetooth_pairing_error_message);
+ }
+
+ public synchronized void onProfileStateChanged(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) return;
+
+ device.refresh();
+ }
+
+ public synchronized void onConnectingError(String address) {
+ LocalBluetoothDevice device = findDevice(address);
+ if (device == null) return;
+
+ /*
+ * Go through the device's delegate so we don't spam the user with
+ * errors connecting to different profiles, and instead make sure the
+ * user sees a single error for his single 'connect' action.
+ */
+ device.showConnectingError();
+ }
+
+ public synchronized void onScanningStateChanged(boolean started) {
+ if (!started) return;
+
+ // If starting a new scan, clear old visibility
+ for (int i = mDevices.size() - 1; i >= 0; i--) {
+ LocalBluetoothDevice device = mDevices.get(i);
+ device.setVisible(false);
+ checkForDeviceRemoval(device);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 0000000000..9db9e77501
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+import android.widget.Toast;
+
+// TODO: have some notion of shutting down. Maybe a minute after they leave BT settings?
+/**
+ * LocalBluetoothManager provides a simplified interface on top of a subset of
+ * the Bluetooth API.
+ */
+public class LocalBluetoothManager {
+ private static final String TAG = "LocalBluetoothManager";
+ static final boolean V = true;
+
+ public static final String EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION =
+ "com.android.settings.bluetooth.intent.action.EXTENDED_BLUETOOTH_STATE_CHANGED";
+ private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
+
+ private static LocalBluetoothManager INSTANCE;
+ /** Used when obtaining a reference to the singleton instance. */
+ private static Object INSTANCE_LOCK = new Object();
+ private boolean mInitialized;
+
+ private Context mContext;
+ /** If a BT-related activity is in the foreground, this will be it. */
+ private Activity mForegroundActivity;
+
+ private BluetoothDevice mManager;
+
+ private LocalBluetoothDeviceManager mLocalDeviceManager;
+ private BluetoothEventRedirector mEventRedirector;
+
+ public static enum ExtendedBluetoothState { ENABLED, ENABLING, DISABLED, DISABLING, UNKNOWN }
+ private ExtendedBluetoothState mState = ExtendedBluetoothState.UNKNOWN;
+
+ private List<Callback> mCallbacks = new ArrayList<Callback>();
+
+ private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
+ private long mLastScan;
+
+ public static LocalBluetoothManager getInstance(Context context) {
+ synchronized (INSTANCE_LOCK) {
+ if (INSTANCE == null) {
+ INSTANCE = new LocalBluetoothManager();
+ }
+
+ if (!INSTANCE.init(context)) {
+ return null;
+ }
+
+ return INSTANCE;
+ }
+ }
+
+ private boolean init(Context context) {
+ if (mInitialized) return true;
+ mInitialized = true;
+
+ // This will be around as long as this process is
+ mContext = context.getApplicationContext();
+
+ mManager = (BluetoothDevice) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (mManager == null) {
+ return false;
+ }
+
+ mLocalDeviceManager = new LocalBluetoothDeviceManager(this);
+
+ mEventRedirector = new BluetoothEventRedirector(this);
+ mEventRedirector.start();
+
+ return true;
+ }
+
+ public BluetoothDevice getBluetoothManager() {
+ return mManager;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Activity getForegroundActivity() {
+ return mForegroundActivity;
+ }
+
+ public void setForegroundActivity(Activity activity) {
+ mForegroundActivity = activity;
+ }
+
+ public SharedPreferences getSharedPreferences() {
+ return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+ }
+
+ public LocalBluetoothDeviceManager getLocalDeviceManager() {
+ return mLocalDeviceManager;
+ }
+
+ List<Callback> getCallbacks() {
+ return mCallbacks;
+ }
+
+ public void registerCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ public void startScanning(boolean force) {
+ if (mManager.isDiscovering()) {
+ /*
+ * Already discovering, but give the callback that information.
+ * Note: we only call the callbacks, not the same path as if the
+ * scanning state had really changed (in that case the device
+ * manager would clear its list of unpaired scanned devices).
+ */
+ dispatchScanningStateChanged(true);
+ } else {
+
+ // Don't scan more than frequently than SCAN_EXPIRATION_MS, unless forced
+ if (!force && mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) return;
+
+ if (mManager.startDiscovery(true)) {
+ mLastScan = System.currentTimeMillis();
+ }
+ }
+ }
+
+ public ExtendedBluetoothState getBluetoothState() {
+
+ if (mState == ExtendedBluetoothState.UNKNOWN) {
+ syncBluetoothState();
+ }
+
+ return mState;
+ }
+
+ void setBluetoothStateInt(ExtendedBluetoothState state) {
+ mState = state;
+
+ /*
+ * TODO: change to callback method. originally it was broadcast to
+ * parallel the framework's method, but it just complicates things here.
+ */
+ // If this were a real API, I'd add as an extra
+ mContext.sendBroadcast(new Intent(EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+
+ if (state == ExtendedBluetoothState.ENABLED || state == ExtendedBluetoothState.DISABLED) {
+ mLocalDeviceManager.onBluetoothStateChanged(state == ExtendedBluetoothState.ENABLED);
+ }
+ }
+
+ private void syncBluetoothState() {
+ setBluetoothStateInt(mManager.isEnabled()
+ ? ExtendedBluetoothState.ENABLED
+ : ExtendedBluetoothState.DISABLED);
+ }
+
+ public void setBluetoothEnabled(boolean enabled) {
+ boolean wasSetStateSuccessful = enabled
+ ? mManager.enable()
+ : mManager.disable();
+
+ if (wasSetStateSuccessful) {
+ setBluetoothStateInt(enabled
+ ? ExtendedBluetoothState.ENABLING
+ : ExtendedBluetoothState.DISABLING);
+ } else {
+ if (V) {
+ Log.v(TAG,
+ "setBluetoothEnabled call, manager didn't return success for enabled: "
+ + enabled);
+ }
+
+ syncBluetoothState();
+ }
+ }
+
+ /**
+ * @param started True if scanning started, false if scanning finished.
+ */
+ void onScanningStateChanged(boolean started) {
+ // TODO: have it be a callback (once we switch bluetooth state changed to callback)
+ mLocalDeviceManager.onScanningStateChanged(started);
+ dispatchScanningStateChanged(started);
+ }
+
+ private void dispatchScanningStateChanged(boolean started) {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onScanningStateChanged(started);
+ }
+ }
+ }
+
+ public boolean createBonding(String address) {
+ return mManager.createBonding(address, mEventRedirector.getBluetoothDeviceCallback());
+ }
+
+ public void showError(String address, int titleResId, int messageResId) {
+ LocalBluetoothDevice device = mLocalDeviceManager.findDevice(address);
+ if (device == null) return;
+
+ String name = device.getName();
+ String message = mContext.getString(messageResId, name);
+
+ if (mForegroundActivity != null) {
+ // Need an activity context to show a dialog
+ AlertDialog ad = new AlertDialog.Builder(mForegroundActivity)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(titleResId)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ } else {
+ // Fallback on a toast
+ Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public interface Callback {
+ void onScanningStateChanged(boolean started);
+ void onDeviceAdded(LocalBluetoothDevice device);
+ void onDeviceDeleted(LocalBluetoothDevice device);
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
new file mode 100644
index 0000000000..b614712b09
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothClass;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LocalBluetoothProfileManager is an abstract class defining the basic
+ * functionality related to a profile.
+ */
+public abstract class LocalBluetoothProfileManager {
+
+ // TODO: close profiles when we're shutting down
+ private static Map<Profile, LocalBluetoothProfileManager> sProfileMap =
+ new HashMap<Profile, LocalBluetoothProfileManager>();
+
+ protected LocalBluetoothManager mLocalManager;
+
+ public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager,
+ Profile profile) {
+
+ LocalBluetoothProfileManager profileManager;
+
+ synchronized (sProfileMap) {
+ profileManager = sProfileMap.get(profile);
+
+ if (profileManager == null) {
+ switch (profile) {
+ case A2DP:
+ profileManager = new A2dpProfileManager(localManager);
+ break;
+
+ case HEADSET:
+ profileManager = new HeadsetProfileManager(localManager);
+ break;
+ }
+
+ sProfileMap.put(profile, profileManager);
+ }
+ }
+
+ return profileManager;
+ }
+
+ // TODO: remove once the framework has this API
+ public static boolean isPreferredProfile(Context context, String address, Profile profile) {
+ return getPreferredProfileSharedPreferences(context).getBoolean(
+ getPreferredProfileKey(address, profile), true);
+ }
+
+ public static void setPreferredProfile(Context context, String address, Profile profile,
+ boolean preferred) {
+ getPreferredProfileSharedPreferences(context).edit().putBoolean(
+ getPreferredProfileKey(address, profile), preferred).commit();
+ }
+
+ private static SharedPreferences getPreferredProfileSharedPreferences(Context context) {
+ return context.getSharedPreferences("bluetooth_preferred_profiles", Context.MODE_PRIVATE);
+ }
+
+ private static String getPreferredProfileKey(String address, Profile profile) {
+ return address + "_" + profile.toString();
+ }
+
+ /**
+ * Temporary method to fill profiles based on a device's class.
+ *
+ * @param btClass The class
+ * @param profiles The list of profiles to fill
+ */
+ public static void fill(int btClass, List<Profile> profiles) {
+ profiles.clear();
+
+ if (A2dpProfileManager.doesClassMatch(btClass)) {
+ profiles.add(Profile.A2DP);
+ }
+
+ if (HeadsetProfileManager.doesClassMatch(btClass)) {
+ profiles.add(Profile.HEADSET);
+ }
+ }
+
+ protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) {
+ mLocalManager = localManager;
+ }
+
+ public abstract int connect(String address);
+
+ public abstract int disconnect(String address);
+
+ public abstract int getConnectionStatus(String address);
+
+ public abstract int getSummary(String address);
+
+ public boolean isConnected(String address) {
+ return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(address));
+ }
+
+ // TODO: int instead of enum
+ public enum Profile {
+ HEADSET(R.string.bluetooth_profile_headset),
+ A2DP(R.string.bluetooth_profile_a2dp);
+
+ public final int localizedString;
+
+ private Profile(int localizedString) {
+ this.localizedString = localizedString;
+ }
+ }
+
+ /**
+ * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service.
+ */
+ private static class A2dpProfileManager extends LocalBluetoothProfileManager {
+ private BluetoothA2dp mService;
+
+ public A2dpProfileManager(LocalBluetoothManager localManager) {
+ super(localManager);
+
+ mService = new BluetoothA2dp(localManager.getContext());
+ // TODO: block until connection?
+ }
+
+ @Override
+ public int connect(String address) {
+ return mService.connectSink(address);
+ }
+
+ @Override
+ public int disconnect(String address) {
+ return mService.disconnectSink(address);
+ }
+
+ static boolean doesClassMatch(int btClass) {
+ if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
+ return true;
+ }
+
+ // By the specification A2DP sinks must indicate the RENDER service
+ // class, but some do not (Chordette). So match on a few more to be
+ // safe
+ switch (BluetoothClass.Device.getDevice(btClass)) {
+ case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
+ case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
+ case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public int getConnectionStatus(String address) {
+ return convertState(mService.getSinkState(address));
+ }
+
+ @Override
+ public int getSummary(String address) {
+ int connectionStatus = getConnectionStatus(address);
+
+ if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
+ return R.string.bluetooth_a2dp_profile_summary_connected;
+ } else {
+ return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+ }
+ }
+
+ private static int convertState(int a2dpState) {
+ switch (a2dpState) {
+ case BluetoothA2dp.STATE_CONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
+ case BluetoothA2dp.STATE_CONNECTING:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ case BluetoothA2dp.STATE_DISCONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+ case BluetoothA2dp.STATE_DISCONNECTING:
+ return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
+ case BluetoothA2dp.STATE_PLAYING:
+ return SettingsBtStatus.CONNECTION_STATUS_ACTIVE;
+ default:
+ return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
+ }
+ }
+ }
+
+ /**
+ * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service.
+ */
+ private static class HeadsetProfileManager extends LocalBluetoothProfileManager {
+ private BluetoothHeadset mService;
+
+ public HeadsetProfileManager(LocalBluetoothManager localManager) {
+ super(localManager);
+
+// final boolean[] isServiceConnected = new boolean[1];
+// BluetoothHeadset.ServiceListener l = new BluetoothHeadset.ServiceListener() {
+// public void onServiceConnected() {
+// synchronized (this) {
+// isServiceConnected[0] = true;
+// notifyAll();
+// }
+// }
+// public void onServiceDisconnected() {
+// mService = null;
+// }
+// };
+
+ // TODO: block, but can't on UI thread
+ mService = new BluetoothHeadset(localManager.getContext(), null);
+
+// synchronized (l) {
+// while (!isServiceConnected[0]) {
+// try {
+// l.wait(100);
+// } catch (InterruptedException e) {
+// throw new IllegalStateException(e);
+// }
+// }
+// }
+ }
+
+ @Override
+ public int connect(String address) {
+ // Since connectHeadset fails if already connected to a headset, we
+ // disconnect from any headset first
+ mService.disconnectHeadset();
+ return mService.connectHeadset(address, null)
+ ? BluetoothError.SUCCESS : BluetoothError.ERROR;
+ }
+
+ @Override
+ public int disconnect(String address) {
+ if (mService.getHeadsetAddress().equals(address)) {
+ return mService.disconnectHeadset() ? BluetoothError.SUCCESS : BluetoothError.ERROR;
+ } else {
+ return BluetoothError.SUCCESS;
+ }
+ }
+
+ static boolean doesClassMatch(int btClass) {
+ switch (BluetoothClass.Device.getDevice(btClass)) {
+ case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+ case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+ case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public int getConnectionStatus(String address) {
+ String headsetAddress = mService.getHeadsetAddress();
+ return headsetAddress != null && headsetAddress.equals(address)
+ ? convertState(mService.getState())
+ : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+ }
+
+ @Override
+ public int getSummary(String address) {
+ int connectionStatus = getConnectionStatus(address);
+
+ if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
+ return R.string.bluetooth_headset_profile_summary_connected;
+ } else {
+ return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+ }
+ }
+
+ private static int convertState(int headsetState) {
+ switch (headsetState) {
+ case BluetoothHeadset.STATE_CONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
+ case BluetoothHeadset.STATE_CONNECTING:
+ return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+ case BluetoothHeadset.STATE_DISCONNECTED:
+ return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+ default:
+ return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/settings/bluetooth/SettingsBtStatus.java b/src/com/android/settings/bluetooth/SettingsBtStatus.java
new file mode 100644
index 0000000000..051d666778
--- /dev/null
+++ b/src/com/android/settings/bluetooth/SettingsBtStatus.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import com.android.settings.R;
+
+/**
+ * SettingsBtStatus is a helper class that contains constants for various status
+ * codes.
+ */
+public class SettingsBtStatus {
+ private static final String TAG = "SettingsBtStatus";
+
+ // Connection status
+
+ public static final int CONNECTION_STATUS_UNKNOWN = 0;
+ public static final int CONNECTION_STATUS_ACTIVE = 1;
+ /** Use {@link #isConnected} to check for the connected state */
+ public static final int CONNECTION_STATUS_CONNECTED = 2;
+ public static final int CONNECTION_STATUS_CONNECTING = 3;
+ public static final int CONNECTION_STATUS_DISCONNECTED = 4;
+ public static final int CONNECTION_STATUS_DISCONNECTING = 5;
+
+ public static final int getConnectionStatusSummary(int connectionStatus) {
+ switch (connectionStatus) {
+ case CONNECTION_STATUS_ACTIVE:
+ return R.string.bluetooth_connected;
+ case CONNECTION_STATUS_CONNECTED:
+ return R.string.bluetooth_connected;
+ case CONNECTION_STATUS_CONNECTING:
+ return R.string.bluetooth_connecting;
+ case CONNECTION_STATUS_DISCONNECTED:
+ return R.string.bluetooth_disconnected;
+ case CONNECTION_STATUS_DISCONNECTING:
+ return R.string.bluetooth_disconnecting;
+ case CONNECTION_STATUS_UNKNOWN:
+ return R.string.bluetooth_unknown;
+ default:
+ return 0;
+ }
+ }
+
+ public static final boolean isConnectionStatusConnected(int connectionStatus) {
+ return connectionStatus == CONNECTION_STATUS_ACTIVE
+ || connectionStatus == CONNECTION_STATUS_CONNECTED;
+ }
+
+ public static final boolean isConnectionStatusBusy(int connectionStatus) {
+ return connectionStatus == CONNECTION_STATUS_CONNECTING
+ || connectionStatus == CONNECTION_STATUS_DISCONNECTING;
+ }
+
+ // Pairing status
+
+ public static final int PAIRING_STATUS_UNPAIRED = 0;
+ public static final int PAIRING_STATUS_PAIRED = 1;
+ public static final int PAIRING_STATUS_PAIRING = 2;
+
+ public static final int getPairingStatusSummary(int pairingStatus) {
+ switch (pairingStatus) {
+ case PAIRING_STATUS_PAIRED:
+ return R.string.bluetooth_paired;
+ case PAIRING_STATUS_PAIRING:
+ return R.string.bluetooth_pairing;
+ case PAIRING_STATUS_UNPAIRED:
+ return R.string.bluetooth_not_connected;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java
index 8a3cb8bfa7..86e642345b 100644
--- a/src/com/android/settings/deviceinfo/Memory.java
+++ b/src/com/android/settings/deviceinfo/Memory.java
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.Environment;
import android.os.IMountService;
@@ -30,6 +31,7 @@ import android.os.StatFs;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
+import android.util.Log;
import com.android.settings.R;
@@ -37,6 +39,8 @@ import java.io.File;
import java.text.DecimalFormat;
public class Memory extends PreferenceActivity {
+
+ private static final String TAG = "Memory";
private static final String MEMORY_SD_SIZE = "memory_sd_size";
@@ -50,15 +54,14 @@ public class Memory extends PreferenceActivity {
private Preference mSdAvail;
private Preference mSdUnmount;
- private IMountService mMountService;
+ // Access using getMountService()
+ private IMountService mMountService = null;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.device_info_memory);
-
- mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
mRes = getResources();
mSdSize = findPreference(MEMORY_SD_SIZE);
@@ -89,6 +92,18 @@ public class Memory extends PreferenceActivity {
unregisterReceiver(mReceiver);
}
+ private synchronized IMountService getMountService() {
+ if (mMountService == null) {
+ IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ mMountService = IMountService.Stub.asInterface(service);
+ } else {
+ Log.e(TAG, "Can't get mount service");
+ }
+ }
+ return mMountService;
+ }
+
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
if (preference == mSdUnmount) {
@@ -107,8 +122,13 @@ public class Memory extends PreferenceActivity {
};
private void unmount() {
+ IMountService mountService = getMountService();
try {
- mMountService.unmountMedia(Environment.getExternalStorageDirectory().toString());
+ if (mountService != null) {
+ mountService.unmountMedia(Environment.getExternalStorageDirectory().toString());
+ } else {
+ Log.e(TAG, "Mount service is null, can't unmount");
+ }
} catch (RemoteException ex) {
// Failed for some reason, try to update UI to actual state
updateMemoryStatus();
diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java
index a56e60761b..b7aceb1d31 100644
--- a/src/com/android/settings/deviceinfo/Status.java
+++ b/src/com/android/settings/deviceinfo/Status.java
@@ -240,9 +240,11 @@ public class Status extends PreferenceActivity {
}
private void setSummaryText(String preference, String text) {
- if (text != null) {
- findPreference(preference).setSummary(text);
+ if (TextUtils.isEmpty(text)) {
+ text = sUnknown;
}
+
+ findPreference(preference).setSummary(text);
}
private void updateNetworkType() {
diff --git a/src/com/android/settings/quicklaunch/QuickLaunchSettings.java b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
index 1b9dff47f5..df15c0b152 100644
--- a/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
+++ b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
@@ -263,7 +263,7 @@ public class QuickLaunchSettings extends PreferenceActivity implements
KeyCharacterMap keyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
// Go through all the key codes and create a preference for the appropriate keys
- for (int keyCode = KeyEvent.MAX_KEYCODE - 1; keyCode >= 0; keyCode--) {
+ for (int keyCode = KeyEvent.getMaxKeyCode() - 1; keyCode >= 0; keyCode--) {
// Get the label for the primary char on the key that produces this key code
char shortcut = (char) Character.toLowerCase(keyMap.getDisplayLabel(keyCode));
if (shortcut == 0 || shortcutSeen.get(shortcut, false)) continue;
diff --git a/src/com/android/settings/wifi/AccessPointDialog.java b/src/com/android/settings/wifi/AccessPointDialog.java
index 95e469fef7..917ed967f3 100644
--- a/src/com/android/settings/wifi/AccessPointDialog.java
+++ b/src/com/android/settings/wifi/AccessPointDialog.java
@@ -25,6 +25,7 @@ import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.text.TextUtils;
+import android.text.format.Formatter;
import android.text.method.PasswordTransformationMethod;
import android.text.method.TransformationMethod;
import android.util.Log;
@@ -303,7 +304,7 @@ public class AccessPointDialog extends AlertDialog implements DialogInterface.On
}
if (mState.primary && mState.ipAddress != 0) {
- addInfoRow(R.string.ip_address, ipAddressToString(mState.ipAddress));
+ addInfoRow(R.string.ip_address, Formatter.formatIpAddress(mState.ipAddress));
}
} else if (mMode == MODE_CONFIGURE) {
@@ -579,14 +580,6 @@ public class AccessPointDialog extends AlertDialog implements DialogInterface.On
return 0;
}
- private static String ipAddressToString(int addr) {
- StringBuffer buf = new StringBuffer();
- buf.append(addr & 0xff).append('.').
- append((addr >>>= 8) & 0xff).append('.').
- append((addr >>>= 8) & 0xff).append('.').
- append((addr >>>= 8) & 0xff);
- return buf.toString();
- }
public void onClick(View v) {
if (v == mShowPasswordCheckBox) {
diff --git a/src/com/android/settings/wifi/AccessPointState.java b/src/com/android/settings/wifi/AccessPointState.java
index 8dabbd1e91..c22495434f 100644
--- a/src/com/android/settings/wifi/AccessPointState.java
+++ b/src/com/android/settings/wifi/AccessPointState.java
@@ -538,7 +538,13 @@ public final class AccessPointState implements Comparable<AccessPointState>, Par
// If password is empty, it should be left untouched
if (!TextUtils.isEmpty(mPassword)) {
- config.preSharedKey = convertToQuotedString(mPassword);
+ if (mPassword.length() == 64 && isHex(mPassword)) {
+ // Goes unquoted as hex
+ config.preSharedKey = mPassword;
+ } else {
+ // Goes quoted as ASCII
+ config.preSharedKey = convertToQuotedString(mPassword);
+ }
}
} else if (security.equals(OPEN)) {
@@ -554,8 +560,12 @@ public final class AccessPointState implements Comparable<AccessPointState>, Par
return false;
}
- for (int i = len - 1; i >= 0; i--) {
- final char c = wepKey.charAt(i);
+ return isHex(wepKey);
+ }
+
+ private static boolean isHex(String key) {
+ for (int i = key.length() - 1; i >= 0; i--) {
+ final char c = key.charAt(i);
if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f')) {
return false;
}
diff --git a/src/com/android/settings/wifi/IpSettings.java b/src/com/android/settings/wifi/IpSettings.java
index 592e8da5a9..5a494fa4ff 100644
--- a/src/com/android/settings/wifi/IpSettings.java
+++ b/src/com/android/settings/wifi/IpSettings.java
@@ -19,12 +19,16 @@ package com.android.settings.wifi;
import com.android.settings.R;
import android.content.ContentResolver;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
+import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.provider.Settings.System;
+import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -32,8 +36,10 @@ import android.widget.Toast;
public class IpSettings extends PreferenceActivity implements Preference.OnPreferenceChangeListener {
+ private static final String KEY_MAC_ADDRESS = "mac_address";
private static final String KEY_USE_STATIC_IP = "use_static_ip";
-
+ private static final String KEY_NUM_CHANNELS = "num_channels";
+
private String[] mSettingNames = {
System.WIFI_STATIC_IP, System.WIFI_STATIC_GATEWAY, System.WIFI_STATIC_NETMASK,
System.WIFI_STATIC_DNS1, System.WIFI_STATIC_DNS2
@@ -61,12 +67,46 @@ public class IpSettings extends PreferenceActivity implements Preference.OnPrefe
preference.setOnPreferenceChangeListener(this);
}
}
-
+
@Override
protected void onResume() {
super.onResume();
updateUi();
+ initNumChannelsPreference();
+ refreshMacAddress();
+ }
+
+ private void initNumChannelsPreference() {
+ ListPreference pref = (ListPreference) findPreference(KEY_NUM_CHANNELS);
+ pref.setOnPreferenceChangeListener(this);
+
+ WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+ /*
+ * Generate the list of valid channel counts to show in the ListPreference.
+ * The values are numerical, so the only text to be localized is the
+ * "channel_word" resource.
+ */
+ int[] validChannelCounts = wifiManager.getValidChannelCounts();
+ if (validChannelCounts == null) {
+ Toast.makeText(this, R.string.wifi_setting_num_channels_error,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+ String[] entries = new String[validChannelCounts.length];
+ String[] entryValues = new String[validChannelCounts.length];
+
+ for (int i = 0; i < validChannelCounts.length; i++) {
+ entryValues[i] = String.valueOf(validChannelCounts[i]);
+ entries[i] = getString(R.string.wifi_setting_num_channels_channel_phrase,
+ validChannelCounts[i]);
+ }
+ pref.setEntries(entries);
+ pref.setEntryValues(entryValues);
+ int numChannels = wifiManager.getNumAllowedChannels();
+ if (numChannels >= 0) {
+ pref.setValue(String.valueOf(numChannels));
+ }
}
@Override
@@ -80,14 +120,34 @@ public class IpSettings extends PreferenceActivity implements Preference.OnPrefe
}
public boolean onPreferenceChange(Preference preference, Object newValue) {
- String value = (String) newValue;
-
- if (!isIpAddress(value)) {
- Toast.makeText(this, R.string.wifi_ip_settings_invalid_ip, Toast.LENGTH_LONG).show();
- return false;
+ String key = preference.getKey();
+ if (key == null) return true;
+
+ if (key.equals(KEY_NUM_CHANNELS)) {
+ try {
+ int numChannels = Integer.parseInt((String) newValue);
+ WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+ if (!wifiManager.setNumAllowedChannels(numChannels)) {
+ Toast.makeText(this, R.string.wifi_setting_num_channels_error,
+ Toast.LENGTH_SHORT).show();
+ }
+ } catch (NumberFormatException e) {
+ Toast.makeText(this, R.string.wifi_setting_num_channels_error,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ } else {
+ String value = (String) newValue;
+
+ if (!isIpAddress(value)) {
+ Toast.makeText(this, R.string.wifi_ip_settings_invalid_ip, Toast.LENGTH_LONG).show();
+ return false;
+ }
+
+ preference.setSummary(value);
}
- preference.setSummary(value);
return true;
}
@@ -177,4 +237,14 @@ public class IpSettings extends PreferenceActivity implements Preference.OnPrefe
}
}
+ private void refreshMacAddress() {
+ WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+
+ Preference wifiMacAddressPref = findPreference(KEY_MAC_ADDRESS);
+ String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+ wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress
+ : getString(R.string.status_unavailable));
+ }
+
}
diff --git a/src/com/android/settings/wifi/WifiLayer.java b/src/com/android/settings/wifi/WifiLayer.java
index b29fd9246a..b0857d2c81 100644
--- a/src/com/android/settings/wifi/WifiLayer.java
+++ b/src/com/android/settings/wifi/WifiLayer.java
@@ -94,7 +94,7 @@ public class WifiLayer {
private boolean mIsObtainingAddress;
/**
- * See {@link Settings.System#WIFI_NUM_OPEN_NETWORKS_KEPT}.
+ * See {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}.
*/
private int WIFI_NUM_OPEN_NETWORKS_KEPT;
/**
@@ -229,8 +229,8 @@ public class WifiLayer {
mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
- WIFI_NUM_OPEN_NETWORKS_KEPT = Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.WIFI_NUM_OPEN_NETWORKS_KEPT, 10);
+ WIFI_NUM_OPEN_NETWORKS_KEPT = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, 10);
}
/**
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index ceb995bf4c..c92ed7cb10 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -134,8 +134,8 @@ public class WifiSettings extends PreferenceActivity implements WifiLayer.Callba
mOpenNetworkNotificationsEnabled = (CheckBoxPreference) preferenceScreen
.findPreference(KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED);
- mOpenNetworkNotificationsEnabled.setChecked(Settings.System.getInt(getContentResolver(),
- Settings.System.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
+ mOpenNetworkNotificationsEnabled.setChecked(Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
mAddOtherNetwork = preferenceScreen.findPreference(KEY_ADD_OTHER_NETWORK);
@@ -323,9 +323,9 @@ public class WifiSettings extends PreferenceActivity implements WifiLayer.Callba
if (preference == mAddOtherNetwork) {
showAddOtherNetworkDialog();
} else if (preference == mOpenNetworkNotificationsEnabled) {
- Settings.System.putInt(getContentResolver(),
- Settings.System.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- mOpenNetworkNotificationsEnabled.isChecked() ? 1 : 0);
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ mOpenNetworkNotificationsEnabled.isChecked() ? 1 : 0);
} else if (preference instanceof AccessPointPreference) {
AccessPointState state = ((AccessPointPreference) preference).getAccessPointState();
showAccessPointDialog(state, AccessPointDialog.MODE_INFO);