diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:06:01 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:06:01 -0800 |
commit | abc48f80d8747b4fc051b7dd364355ee667a9bac (patch) | |
tree | 31ae577fe29d75963b071e738703e4db83ad6580 /src/com/android | |
parent | de2d9f5f109265873196f1615e1f3546b114aaa7 (diff) | |
download | packages_apps_settings-abc48f80d8747b4fc051b7dd364355ee667a9bac.zip packages_apps_settings-abc48f80d8747b4fc051b7dd364355ee667a9bac.tar.gz packages_apps_settings-abc48f80d8747b4fc051b7dd364355ee667a9bac.tar.bz2 |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'src/com/android')
49 files changed, 4689 insertions, 2164 deletions
diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java index f47d679..0bd950b 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 9b74eea..f1fa2ef 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 ea13ee7..83efa3f 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 0ee2789..85fe11f 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 ef60fc3..ed3e3e3 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 1c7e0b6..0000000 --- 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 c75be13..0000000 --- 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 8933c03..0000000 --- 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 d6a12f3..0000000 --- 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 6a19ec4..0000000 --- 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 973e655..3097e96 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 5feba4c..77517b9 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 9687b55..a0a878a 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 ce9dd8c..ead38d1 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 02b852b..155f085 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 58fc91e..5d72afc 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 0000000..d38779d --- /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 712f94d..d3e7344 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 46d9b52..9ee8260 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 8389502..f1550f9 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 d320e73..80fe3c9 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 ad30de7..b1ad777 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 0000000..2d21ec6 --- /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 c1a509a..a0a52a2 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 82eadca..c40dd07 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 887fb8f..134e84f 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 18b30bd..d112915 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 0000000..f0a8189 --- /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 0000000..f895696 --- /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 0000000..661700f --- /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 0000000..bcad206 --- /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 0000000..3065b26 --- /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 0000000..291d0c1 --- /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 0000000..619052d --- /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 0000000..316e831 --- /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 0000000..f29ec79 --- /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 0000000..a8f79ff --- /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 0000000..48a41f1 --- /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 0000000..9db9e77 --- /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 0000000..b614712 --- /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 0000000..051d666 --- /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 8a3cb8b..86e6423 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 a56e607..b7aceb1 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 1b9dff4..df15c0b 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 95e469f..917ed96 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 8dabbd1..c224954 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 592e8da..5a494fa 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 b29fd92..b0857d2 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 ceb995b..c92ed7c 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); |