diff options
Diffstat (limited to 'src/com/android')
71 files changed, 5101 insertions, 3498 deletions
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java index 9328c149b..f738823f0 100644 --- a/src/com/android/settings/ApnEditor.java +++ b/src/com/android/settings/ApnEditor.java @@ -48,6 +48,7 @@ public class ApnEditor extends PreferenceActivity private final static String SAVED_POS = "pos"; private final static String KEY_AUTH_TYPE = "auth_type"; + private final static String KEY_PROTOCOL = "apn_protocol"; private static final int MENU_DELETE = Menu.FIRST; private static final int MENU_SAVE = Menu.FIRST + 1; @@ -69,6 +70,7 @@ public class ApnEditor extends PreferenceActivity private EditTextPreference mMmsPort; private ListPreference mAuthType; private EditTextPreference mApnType; + private ListPreference mProtocol; private String mCurMnc; private String mCurMcc; @@ -99,6 +101,7 @@ public class ApnEditor extends PreferenceActivity Telephony.Carriers.MMSPORT, // 13 Telephony.Carriers.AUTH_TYPE, // 14 Telephony.Carriers.TYPE, // 15 + Telephony.Carriers.PROTOCOL, // 16 }; private static final int ID_INDEX = 0; @@ -116,6 +119,7 @@ public class ApnEditor extends PreferenceActivity private static final int MMSPORT_INDEX = 13; private static final int AUTH_TYPE_INDEX = 14; private static final int TYPE_INDEX = 15; + private static final int PROTOCOL_INDEX = 16; @Override @@ -139,9 +143,12 @@ public class ApnEditor extends PreferenceActivity mMnc = (EditTextPreference) findPreference("apn_mnc"); mApnType = (EditTextPreference) findPreference("apn_type"); - mAuthType = (ListPreference) findPreference("auth_type"); + mAuthType = (ListPreference) findPreference(KEY_AUTH_TYPE); mAuthType.setOnPreferenceChangeListener(this); + mProtocol = (ListPreference) findPreference(KEY_PROTOCOL); + mProtocol.setOnPreferenceChangeListener(this); + mRes = getResources(); final Intent intent = getIntent(); @@ -238,6 +245,7 @@ public class ApnEditor extends PreferenceActivity mAuthType.setValue(null); } + mProtocol.setValue(mCursor.getString(PROTOCOL_INDEX)); } mName.setSummary(checkNull(mName.getText())); @@ -264,6 +272,28 @@ public class ApnEditor extends PreferenceActivity } else { mAuthType.setSummary(sNotSet); } + + mProtocol.setSummary( + checkNull(protocolDescription(mProtocol.getValue()))); + } + + /** + * Returns the UI choice (e.g., "IPv4/IPv6") corresponding to the given + * raw value of the protocol preference (e.g., "IPV4V6"). If unknown, + * return null. + */ + private String protocolDescription(String raw) { + int protocolIndex = mProtocol.findIndexOfValue(raw); + if (protocolIndex == -1) { + return null; + } else { + String[] values = mRes.getStringArray(R.array.apn_protocol_entries); + try { + return values[protocolIndex]; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } } public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -278,6 +308,16 @@ public class ApnEditor extends PreferenceActivity } catch (NumberFormatException e) { return false; } + return true; + } + + if (KEY_PROTOCOL.equals(key)) { + String protocol = protocolDescription((String) newValue); + if (protocol == null) { + return false; + } + mProtocol.setSummary(protocol); + mProtocol.setValue((String) newValue); } return true; } @@ -389,6 +429,8 @@ public class ApnEditor extends PreferenceActivity values.put(Telephony.Carriers.AUTH_TYPE, Integer.parseInt(authVal)); } + values.put(Telephony.Carriers.PROTOCOL, checkNotSet(mProtocol.getValue())); + values.put(Telephony.Carriers.TYPE, checkNotSet(mApnType.getText())); values.put(Telephony.Carriers.MCC, mcc); diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java index 00f5debc2..9bbb66ac4 100644 --- a/src/com/android/settings/BrightnessPreference.java +++ b/src/com/android/settings/BrightnessPreference.java @@ -16,25 +16,25 @@ package com.android.settings; +import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; +import android.os.Bundle; +import android.os.Handler; +import android.os.IPowerManager; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.os.IPowerManager; import android.os.ServiceManager; import android.preference.SeekBarPreference; -import android.preference.Preference.BaseSavedState; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.SeekBar; -import java.util.Map; - public class BrightnessPreference extends SeekBarPreference implements SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener { @@ -53,6 +53,20 @@ public class BrightnessPreference extends SeekBarPreference implements private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 10; private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON; + private ContentObserver mBrightnessObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + onBrightnessChanged(); + } + }; + + private ContentObserver mBrightnessModeObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + onBrightnessModeChanged(); + } + }; + public BrightnessPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -64,28 +78,32 @@ public class BrightnessPreference extends SeekBarPreference implements } @Override + protected void showDialog(Bundle state) { + super.showDialog(state); + + getContext().getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS), true, + mBrightnessObserver); + + getContext().getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE), true, + mBrightnessModeObserver); + mRestoredOldState = false; + } + + @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); mSeekBar = getSeekBar(view); mSeekBar.setMax(MAXIMUM_BACKLIGHT - MINIMUM_BACKLIGHT); - try { - mOldBrightness = Settings.System.getInt(getContext().getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS); - } catch (SettingNotFoundException snfe) { - mOldBrightness = MAXIMUM_BACKLIGHT; - } + mOldBrightness = getBrightness(0); mSeekBar.setProgress(mOldBrightness - MINIMUM_BACKLIGHT); mCheckBox = (CheckBox)view.findViewById(R.id.automatic_mode); if (mAutomaticAvailable) { mCheckBox.setOnCheckedChangeListener(this); - try { - mOldAutomatic = Settings.System.getInt(getContext().getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_MODE); - } catch (SettingNotFoundException snfe) { - mOldAutomatic = 0; - } + mOldAutomatic = getBrightnessMode(0); mCheckBox.setChecked(mOldAutomatic != 0); } else { mCheckBox.setVisibility(View.GONE); @@ -114,17 +132,52 @@ public class BrightnessPreference extends SeekBarPreference implements } } + private int getBrightness(int defaultValue) { + int brightness = defaultValue; + try { + brightness = Settings.System.getInt(getContext().getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS); + } catch (SettingNotFoundException snfe) { + } + return brightness; + } + + private int getBrightnessMode(int defaultValue) { + int brightnessMode = defaultValue; + try { + brightnessMode = Settings.System.getInt(getContext().getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE); + } catch (SettingNotFoundException snfe) { + } + return brightnessMode; + } + + private void onBrightnessChanged() { + int brightness = getBrightness(MAXIMUM_BACKLIGHT); + mSeekBar.setProgress(brightness - MINIMUM_BACKLIGHT); + } + + private void onBrightnessModeChanged() { + boolean checked = getBrightnessMode(0) != 0; + mCheckBox.setChecked(checked); + } + @Override protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); + final ContentResolver resolver = getContext().getContentResolver(); + if (positiveResult) { - Settings.System.putInt(getContext().getContentResolver(), + Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS, mSeekBar.getProgress() + MINIMUM_BACKLIGHT); } else { restoreOldState(); } + + resolver.unregisterContentObserver(mBrightnessObserver); + resolver.unregisterContentObserver(mBrightnessModeObserver); } private void restoreOldState() { diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java index 018dfd2aa..a0f23465e 100644 --- a/src/com/android/settings/ChooseLockPassword.java +++ b/src/com/android/settings/ChooseLockPassword.java @@ -274,7 +274,7 @@ public class ChooseLockPassword extends PreferenceActivity { if (password.length() > mPasswordMaxLength) { return getString(mIsAlphaMode ? R.string.lockpassword_password_too_long - : R.string.lockpassword_pin_too_long, mPasswordMaxLength); + : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1); } int letters = 0; int numbers = 0; diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java index 17d3de24b..55f62546a 100644 --- a/src/com/android/settings/ChooseLockPattern.java +++ b/src/com/android/settings/ChooseLockPattern.java @@ -248,7 +248,7 @@ public class ChooseLockPattern extends PreferenceActivity { LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false), NeedToConfirm( R.string.lockpattern_need_to_confirm, - LeftButtonMode.CancelDisabled, RightButtonMode.ConfirmDisabled, + LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled, ID_EMPTY_MESSAGE, true), ConfirmWrong( R.string.lockpattern_need_to_unlock_wrong, diff --git a/src/com/android/settings/ChooseLockPatternTutorial.java b/src/com/android/settings/ChooseLockPatternTutorial.java index 2e596dfb7..4ad16fbdc 100644 --- a/src/com/android/settings/ChooseLockPatternTutorial.java +++ b/src/com/android/settings/ChooseLockPatternTutorial.java @@ -102,6 +102,7 @@ public class ChooseLockPatternTutorial extends PreferenceActivity { Intent intent = new Intent(getActivity(), ChooseLockPattern.class); intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); startActivity(intent); + getActivity().overridePendingTransition(0, 0); // no animation getActivity().finish(); } } diff --git a/src/com/android/settings/CryptKeeper.java b/src/com/android/settings/CryptKeeper.java index 3d752a32b..edf00d525 100644 --- a/src/com/android/settings/CryptKeeper.java +++ b/src/com/android/settings/CryptKeeper.java @@ -25,7 +25,9 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.graphics.Rect; import android.inputmethodservice.KeyboardView; +import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -35,11 +37,14 @@ import android.os.ServiceManager; import android.os.SystemProperties; import android.os.storage.IMountService; import android.text.TextUtils; +import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; @@ -59,6 +64,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList private int mCooldown; PowerManager.WakeLock mWakeLock; + private EditText mPasswordEntry; /** * Used to propagate state through configuration changes (e.g. screen rotation) @@ -80,6 +86,76 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } } + // Use a custom EditText to prevent the input method from showing. + public static class CryptEditText extends EditText { + InputMethodManager imm; + + public CryptEditText(Context context, AttributeSet attrs) { + super(context, attrs); + imm = ((InputMethodManager) getContext(). + getSystemService(Context.INPUT_METHOD_SERVICE)); + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(focused, direction, previouslyFocusedRect); + + if (focused && imm != null && imm.isActive(this)) { + imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean handled = super.onTouchEvent(event); + + if (imm != null && imm.isActive(this)) { + imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0); + } + + return handled; + } + } + + private class DecryptTask extends AsyncTask<String, Void, Integer> { + @Override + protected Integer doInBackground(String... params) { + IMountService service = getMountService(); + try { + return service.decryptStorage(params[0]); + } catch (Exception e) { + Log.e(TAG, "Error while decrypting...", e); + return -1; + } + } + + @Override + protected void onPostExecute(Integer failedAttempts) { + if (failedAttempts == 0) { + // The password was entered successfully. Start the Blank activity + // so this activity animates to black before the devices starts. Note + // It has 1 second to complete the animation or it will be frozen + // until the boot animation comes back up. + Intent intent = new Intent(CryptKeeper.this, Blank.class); + finish(); + startActivity(intent); + } else if (failedAttempts == MAX_FAILED_ATTEMPTS) { + // Factory reset the device. + sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); + } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) { + mCooldown = COOL_DOWN_INTERVAL; + cooldown(); + } else { + TextView tv = (TextView) findViewById(R.id.status); + tv.setText(R.string.try_again); + tv.setVisibility(View.VISIBLE); + + // Reenable the password entry + mPasswordEntry.setEnabled(true); + } + } + } + private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -245,8 +321,7 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList if (mCooldown <= 0) { // Re-enable the password entry - EditText passwordEntry = (EditText) findViewById(R.id.passwordEntry); - passwordEntry.setEnabled(true); + mPasswordEntry.setEnabled(true); tv.setVisibility(View.GONE); } else { @@ -262,13 +337,13 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList } private void passwordEntryInit() { - TextView passwordEntry = (TextView) findViewById(R.id.passwordEntry); - passwordEntry.setOnEditorActionListener(this); + mPasswordEntry = (EditText) findViewById(R.id.passwordEntry); + mPasswordEntry.setOnEditorActionListener(this); KeyboardView keyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard); PasswordEntryKeyboardHelper keyboardHelper = new PasswordEntryKeyboardHelper(this, - keyboardView, passwordEntry, false); + keyboardView, mPasswordEntry, false); keyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA); } @@ -293,34 +368,12 @@ public class CryptKeeper extends Activity implements TextView.OnEditorActionList // Now that we have the password clear the password field. v.setText(null); - IMountService service = getMountService(); - try { - int failedAttempts = service.decryptStorage(password); - - if (failedAttempts == 0) { - // The password was entered successfully. Start the Blank activity - // so this activity animates to black before the devices starts. Note - // It has 1 second to complete the animation or it will be frozen - // until the boot animation comes back up. - Intent intent = new Intent(this, Blank.class); - finish(); - startActivity(intent); - } else if (failedAttempts == MAX_FAILED_ATTEMPTS) { - // Factory reset the device. - sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR")); - } else if ((failedAttempts % COOL_DOWN_ATTEMPTS) == 0) { - mCooldown = COOL_DOWN_INTERVAL; - EditText passwordEntry = (EditText) findViewById(R.id.passwordEntry); - passwordEntry.setEnabled(false); - cooldown(); - } else { - TextView tv = (TextView) findViewById(R.id.status); - tv.setText(R.string.try_again); - tv.setVisibility(View.VISIBLE); - } - } catch (Exception e) { - Log.e(TAG, "Error while decrypting...", e); - } + // Disable the password entry while checking the password. This + // we either be reenabled if the password was wrong or after the + // cooldown period. + mPasswordEntry.setEnabled(false); + + new DecryptTask().execute(password); return true; } diff --git a/src/com/android/settings/DateTimeSettingsSetupWizard.java b/src/com/android/settings/DateTimeSettingsSetupWizard.java index c3d3bfdec..bfbb601ed 100644 --- a/src/com/android/settings/DateTimeSettingsSetupWizard.java +++ b/src/com/android/settings/DateTimeSettingsSetupWizard.java @@ -95,7 +95,7 @@ public class DateTimeSettingsSetupWizard extends Activity mAutoTimeZoneButton.setText(autoTimeZoneEnabled ? R.string.zone_auto_summaryOn : R.string.zone_auto_summaryOff);*/ - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); final TimeZone tz = TimeZone.getDefault(); mSelectedTimeZone = tz; diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java index 0e17810c0..cdb0147c1 100644 --- a/src/com/android/settings/DisplaySettings.java +++ b/src/com/android/settings/DisplaySettings.java @@ -21,7 +21,9 @@ import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.preference.CheckBoxPreference; @@ -53,6 +55,13 @@ public class DisplaySettings extends SettingsPreferenceFragment implements private ListPreference mScreenTimeoutPreference; + private ContentObserver mAccelerometerRotationObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + updateAccelerometerRotationCheckbox(); + } + }; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -131,6 +140,16 @@ public class DisplaySettings extends SettingsPreferenceFragment implements super.onResume(); updateState(true); + getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), true, + mAccelerometerRotationObserver); + } + + @Override + public void onPause() { + super.onPause(); + + getContentResolver().unregisterContentObserver(mAccelerometerRotationObserver); } private void updateState(boolean force) { @@ -159,6 +178,10 @@ public class DisplaySettings extends SettingsPreferenceFragment implements } mAnimations.setValueIndex(idx); updateAnimationsSummary(mAnimations.getValue()); + updateAccelerometerRotationCheckbox(); + } + + private void updateAccelerometerRotationCheckbox() { mAccelerometer.setChecked(Settings.System.getInt( getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) != 0); diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java index beea51ec3..4f8b374a9 100644 --- a/src/com/android/settings/RadioInfo.java +++ b/src/com/android/settings/RadioInfo.java @@ -427,7 +427,7 @@ public class RadioInfo extends Activity { if (-1 == signalDbm) signalDbm = 0; - int signalAsu = mPhoneStateReceiver.getSignalStrength(); + int signalAsu = mPhoneStateReceiver.getSignalStrengthLevelAsu(); if (-1 == signalAsu) signalAsu = 0; diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java index 085a089bb..af831576b 100644 --- a/src/com/android/settings/TetherSettings.java +++ b/src/com/android/settings/TetherSettings.java @@ -23,6 +23,7 @@ import android.app.AlertDialog; import android.app.Dialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -34,8 +35,6 @@ import android.os.Environment; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceScreen; -import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.webkit.WebView; @@ -48,7 +47,6 @@ import java.util.Locale; * Displays preferences for Tethering. */ public class TetherSettings extends SettingsPreferenceFragment { - private static final String TAG = "TetheringSettings"; private static final String USB_TETHER_SETTINGS = "usb_tether_settings"; private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; @@ -65,8 +63,6 @@ public class TetherSettings extends SettingsPreferenceFragment { private WebView mView; private CheckBoxPreference mUsbTether; - private CheckBoxPreference mEnableWifiAp; - private PreferenceScreen mWifiApSettings; private WifiApEnabler mWifiApEnabler; private CheckBoxPreference mBluetoothTether; @@ -86,18 +82,17 @@ public class TetherSettings extends SettingsPreferenceFragment { public void onCreate(Bundle icicle) { super.onCreate(icicle); addPreferencesFromResource(R.xml.tether_prefs); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = super.onCreateView(inflater, container, savedInstanceState); final Activity activity = getActivity(); - mBluetoothPan = new BluetoothPan(activity); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener, + BluetoothProfile.PAN); + } - mEnableWifiAp = (CheckBoxPreference) findPreference(ENABLE_WIFI_AP); - mWifiApSettings = (PreferenceScreen) findPreference(WIFI_AP_SETTINGS); + CheckBoxPreference enableWifiAp = + (CheckBoxPreference) findPreference(ENABLE_WIFI_AP); + Preference wifiApSettings = findPreference(WIFI_AP_SETTINGS); mUsbTether = (CheckBoxPreference) findPreference(USB_TETHER_SETTINGS); mBluetoothTether = (CheckBoxPreference) findPreference(ENABLE_BLUETOOTH_TETHERING); mTetherHelp = (PreferenceScreen) findPreference(TETHERING_HELP); @@ -109,49 +104,43 @@ public class TetherSettings extends SettingsPreferenceFragment { mWifiRegexs = cm.getTetherableWifiRegexs(); mBluetoothRegexs = cm.getTetherableBluetoothRegexs(); - boolean usbAvailable = mUsbRegexs.length != 0; - boolean wifiAvailable = mWifiRegexs.length != 0; - boolean bluetoothAvailable = mBluetoothRegexs.length != 0; + final boolean usbAvailable = mUsbRegexs.length != 0; + final boolean wifiAvailable = mWifiRegexs.length != 0; + final boolean bluetoothAvailable = mBluetoothRegexs.length != 0; if (!usbAvailable || Utils.isMonkeyRunning()) { getPreferenceScreen().removePreference(mUsbTether); } + if (!wifiAvailable) { - getPreferenceScreen().removePreference(mEnableWifiAp); - getPreferenceScreen().removePreference(mWifiApSettings); + getPreferenceScreen().removePreference(enableWifiAp); + getPreferenceScreen().removePreference(wifiApSettings); } + if (!bluetoothAvailable) { getPreferenceScreen().removePreference(mBluetoothTether); } else { - if (mBluetoothPan.isTetheringOn()) { + if (mBluetoothPan != null && mBluetoothPan.isTetheringOn()) { mBluetoothTether.setChecked(true); } else { mBluetoothTether.setChecked(false); } } - /* Don't change the title for two-pane settings - if (wifiAvailable && usbAvailable && bluetoothAvailable){ - activity.setTitle(R.string.tether_settings_title_all); - } else if (wifiAvailable && usbAvailable){ - activity.setTitle(R.string.tether_settings_title_all); - } else if (wifiAvailable && bluetoothAvailable){ - activity.setTitle(R.string.tether_settings_title_all); - } else if (wifiAvailable) { - activity.setTitle(R.string.tether_settings_title_wifi); - } else if (usbAvailable && bluetoothAvailable) { - activity.setTitle(R.string.tether_settings_title_usb_bluetooth); - } else if (usbAvailable) { - activity.setTitle(R.string.tether_settings_title_usb); - } else { - activity.setTitle(R.string.tether_settings_title_bluetooth); - } - */ - mWifiApEnabler = new WifiApEnabler(activity, mEnableWifiAp); - mView = new WebView(activity); - return view; + mWifiApEnabler = new WifiApEnabler(activity, enableWifiAp); + mView = new WebView(activity); } + private BluetoothProfile.ServiceListener mProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothPan = (BluetoothPan) proxy; + } + public void onServiceDisconnected(int profile) { + mBluetoothPan = null; + } + }; + @Override public Dialog onCreateDialog(int id) { if (id == DIALOG_TETHER_HELP) { @@ -160,22 +149,22 @@ public class TetherSettings extends SettingsPreferenceFragment { // check for the full language + country resource, if not there, try just language final AssetManager am = getActivity().getAssets(); String path = HELP_PATH.replace("%y", locale.getLanguage().toLowerCase()); - path = path.replace("%z", "_"+locale.getCountry().toLowerCase()); + path = path.replace("%z", '_'+locale.getCountry().toLowerCase()); boolean useCountry = true; InputStream is = null; try { is = am.open(path); - } catch (Exception e) { + } catch (Exception ignored) { useCountry = false; } finally { if (is != null) { try { is.close(); - } catch (Exception e) {} + } catch (Exception ignored) {} } } String url = HELP_URL.replace("%y", locale.getLanguage().toLowerCase()); - url = url.replace("%z", (useCountry ? "_"+locale.getCountry().toLowerCase() : "")); + url = url.replace("%z", useCountry ? '_'+locale.getCountry().toLowerCase() : ""); if ((mUsbRegexs.length != 0) && (mWifiRegexs.length == 0)) { url = url.replace("%x", USB_HELP_MODIFIER); } else if ((mWifiRegexs.length != 0) && (mUsbRegexs.length == 0)) { @@ -277,10 +266,8 @@ public class TetherSettings extends SettingsPreferenceFragment { String[] errored) { ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); - boolean usbTethered = false; boolean usbAvailable = false; int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR; - boolean usbErrored = false; boolean massStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState()); for (String s : available) { @@ -293,11 +280,13 @@ public class TetherSettings extends SettingsPreferenceFragment { } } } + boolean usbTethered = false; for (String s : tethered) { for (String regex : mUsbRegexs) { if (s.matches(regex)) usbTethered = true; } } + boolean usbErrored = false; for (String s: errored) { for (String regex : mUsbRegexs) { if (s.matches(regex)) usbErrored = true; @@ -335,25 +324,23 @@ public class TetherSettings extends SettingsPreferenceFragment { String[] errored) { ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); - boolean bluetoothTethered = false; - boolean bluetoothAvailable = false; int bluetoothError = ConnectivityManager.TETHER_ERROR_NO_ERROR; - boolean bluetoothErrored = false; for (String s : available) { for (String regex : mBluetoothRegexs) { if (s.matches(regex)) { - bluetoothAvailable = true; if (bluetoothError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { bluetoothError = cm.getLastTetherError(s); } } } } + boolean bluetoothTethered = false; for (String s : tethered) { for (String regex : mBluetoothRegexs) { if (s.matches(regex)) bluetoothTethered = true; } } + boolean bluetoothErrored = false; for (String s: errored) { for (String regex : mBluetoothRegexs) { if (s.matches(regex)) bluetoothErrored = true; @@ -464,7 +451,7 @@ public class TetherSettings extends SettingsPreferenceFragment { return super.onPreferenceTreeClick(screen, preference); } - private String findIface(String[] ifaces, String[] regexes) { + private static String findIface(String[] ifaces, String[] regexes) { for (String iface : ifaces) { for (String regex : regexes) { if (iface.matches(regex)) { diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java index b1b7e6ec1..4b927491c 100644 --- a/src/com/android/settings/WirelessSettings.java +++ b/src/com/android/settings/WirelessSettings.java @@ -19,6 +19,8 @@ package com.android.settings; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; import com.android.settings.bluetooth.BluetoothEnabler; +import com.android.settings.bluetooth.LocalBluetoothAdapter; +import com.android.settings.bluetooth.LocalBluetoothManager; import com.android.settings.wifi.WifiEnabler; import com.android.settings.nfc.NfcEnabler; @@ -103,7 +105,8 @@ public class WirelessSettings extends SettingsPreferenceFragment { mAirplaneModeEnabler = new AirplaneModeEnabler(activity, airplane); mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); mWifiEnabler = new WifiEnabler(activity, wifi); - mBtEnabler = new BluetoothEnabler(activity, bt); + mBtEnabler = new BluetoothEnabler(activity, + LocalBluetoothManager.getInstance(activity).getBluetoothAdapter(), bt); mNfcEnabler = new NfcEnabler(activity, nfc); String toggleable = Settings.System.getString(activity.getContentResolver(), diff --git a/src/com/android/settings/accounts/SyncStateCheckBoxPreference.java b/src/com/android/settings/accounts/SyncStateCheckBoxPreference.java index b200eb67d..4b9ca81b2 100644 --- a/src/com/android/settings/accounts/SyncStateCheckBoxPreference.java +++ b/src/com/android/settings/accounts/SyncStateCheckBoxPreference.java @@ -20,6 +20,7 @@ import com.android.settings.R; import android.content.Context; import android.graphics.drawable.AnimationDrawable; +import android.os.Handler; import android.preference.CheckBoxPreference; import android.util.AttributeSet; import android.view.View; @@ -40,7 +41,7 @@ public class SyncStateCheckBoxPreference extends CheckBoxPreference { * toggling whether the provider will do autosync. */ private boolean mOneTimeSyncMode = false; - + public SyncStateCheckBoxPreference(Context context, AttributeSet attrs) { super(context, attrs); setWidgetLayoutResource(R.layout.preference_widget_sync_toggle); @@ -67,7 +68,7 @@ public class SyncStateCheckBoxPreference extends CheckBoxPreference { boolean showError; boolean showPending; if (mIsActive) { - syncActiveView.post(new Runnable() { + new Handler(getContext().getMainLooper()).post(new Runnable() { public void run() { anim.start(); } @@ -87,11 +88,11 @@ public class SyncStateCheckBoxPreference extends CheckBoxPreference { syncFailedView.setVisibility(showError ? View.VISIBLE : View.GONE); syncPendingView.setVisibility((showPending && !mIsActive) ? View.VISIBLE : View.GONE); - + View checkBox = view.findViewById(android.R.id.checkbox); if (mOneTimeSyncMode) { checkBox.setVisibility(View.GONE); - + /* * Override the summary. Fill in the %1$s with the existing summary * (what ends up happening is the old summary is shown on the next @@ -138,7 +139,7 @@ public class SyncStateCheckBoxPreference extends CheckBoxPreference { mOneTimeSyncMode = oneTimeSyncMode; notifyChanged(); } - + /** * Gets whether the preference is in one-time sync mode. */ @@ -152,7 +153,7 @@ public class SyncStateCheckBoxPreference extends CheckBoxPreference { // checkbox state if (!mOneTimeSyncMode) { super.onClick(); - } + } } public Account getAccount() { diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java index 0168396fa..e56db743b 100644 --- a/src/com/android/settings/applications/ApplicationsState.java +++ b/src/com/android/settings/applications/ApplicationsState.java @@ -5,11 +5,14 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageStatsObserver; import android.content.pm.PackageManager; import android.content.pm.PackageStats; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Handler; @@ -200,6 +203,7 @@ public class ApplicationsState { // Information about all applications. Synchronize on mAppEntries // to protect access to these. + final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); final HashMap<String, AppEntry> mEntriesMap = new HashMap<String, AppEntry>(); final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); @@ -376,9 +380,18 @@ public class ApplicationsState { if (mApplications == null) { mApplications = new ArrayList<ApplicationInfo>(); } - for (int i=0; i<mAppEntries.size(); i++) { - mAppEntries.get(i).sizeStale = true; + + if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { + // If an interesting part of the configuration has changed, we + // should completely reload the app entries. + mEntriesMap.clear(); + mAppEntries.clear(); + } else { + for (int i=0; i<mAppEntries.size(); i++) { + mAppEntries.get(i).sizeStale = true; + } } + for (int i=0; i<mApplications.size(); i++) { final ApplicationInfo info = mApplications.get(i); final AppEntry entry = mEntriesMap.get(info.packageName); diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java index 06d97fc87..aafd3a174 100644 --- a/src/com/android/settings/applications/InstalledAppDetails.java +++ b/src/com/android/settings/applications/InstalledAppDetails.java @@ -37,13 +37,16 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.hardware.usb.IUsbManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.preference.PreferenceActivity; import android.text.format.Formatter; import android.util.Log; @@ -79,6 +82,7 @@ public class InstalledAppDetails extends Fragment public static final String ARG_PACKAGE_NAME = "package"; private PackageManager mPm; + private IUsbManager mUsbManager; private ApplicationsState mState; private ApplicationsState.AppEntry mAppEntry; private PackageInfo mPackageInfo; @@ -305,7 +309,9 @@ public class InstalledAppDetails extends Fragment mState = ApplicationsState.getInstance(getActivity().getApplication()); mPm = getActivity().getPackageManager(); - + IBinder b = ServiceManager.getService(Context.USB_SERVICE); + mUsbManager = IUsbManager.Stub.asInterface(b); + mCanBeOnSdCardChecker = new CanBeOnSdCardChecker(); } @@ -444,8 +450,14 @@ public class InstalledAppDetails extends Fragment List<IntentFilter> intentList = new ArrayList<IntentFilter>(); mPm.getPreferredActivities(intentList, prefActList, packageName); if(localLOGV) Log.i(TAG, "Have "+prefActList.size()+" number of activities in prefered list"); + boolean hasUsbDefaults = false; + try { + hasUsbDefaults = mUsbManager.hasDefaults(packageName, mAppEntry.info.uid); + } catch (RemoteException e) { + Log.e(TAG, "mUsbManager.hasDefaults", e); + } TextView autoLaunchView = (TextView)mRootView.findViewById(R.id.auto_launch); - if (prefActList.size() <= 0) { + if (prefActList.size() <= 0 && !hasUsbDefaults) { // Disable clear activities button autoLaunchView.setText(R.string.auto_launch_disable_text); mActivitiesButton.setEnabled(false); @@ -730,18 +742,28 @@ public class InstalledAppDetails extends Fragment private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mForceStopButton.setEnabled(getResultCode() != Activity.RESULT_CANCELED); - mForceStopButton.setOnClickListener(InstalledAppDetails.this); + updateForceStopButton(getResultCode() != Activity.RESULT_CANCELED); } }; + + private void updateForceStopButton(boolean enabled) { + mForceStopButton.setEnabled(enabled); + mForceStopButton.setOnClickListener(InstalledAppDetails.this); + } private void checkForceStop() { Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART, Uri.fromParts("package", mAppEntry.info.packageName, null)); intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName }); intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid); - getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null, - Activity.RESULT_CANCELED, null, null); + if ((mAppEntry.info.flags&ApplicationInfo.FLAG_STOPPED) == 0) { + // If the app isn't explicitly stopped, then always show the + // force stop button. + updateForceStopButton(true); + } else { + getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null, + Activity.RESULT_CANCELED, null, null); + } } static class DisableChanger extends AsyncTask<Object, Object, Object> { @@ -784,6 +806,11 @@ public class InstalledAppDetails extends Fragment } } else if(v == mActivitiesButton) { mPm.clearPackagePreferredActivities(packageName); + try { + mUsbManager.clearDefaults(packageName, mAppEntry.info.uid); + } catch (RemoteException e) { + Log.e(TAG, "mUsbManager.clearDefaults", e); + } mActivitiesButton.setEnabled(false); } else if(v == mClearDataButton) { if (mAppEntry.info.manageSpaceActivityName != null) { diff --git a/src/com/android/settings/applications/InterestingConfigChanges.java b/src/com/android/settings/applications/InterestingConfigChanges.java new file mode 100644 index 000000000..816d16986 --- /dev/null +++ b/src/com/android/settings/applications/InterestingConfigChanges.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.applications; + +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; + +class InterestingConfigChanges { + final Configuration mLastConfiguration = new Configuration(); + int mLastDensity; + + boolean applyNewConfig(Resources res) { + int configChanges = mLastConfiguration.updateFrom(res.getConfiguration()); + boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi; + if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE + |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) { + mLastDensity = res.getDisplayMetrics().densityDpi; + return true; + } + return false; + } +} diff --git a/src/com/android/settings/applications/RunningState.java b/src/com/android/settings/applications/RunningState.java index dbe4a64c0..e7e3af4df 100644 --- a/src/com/android/settings/applications/RunningState.java +++ b/src/com/android/settings/applications/RunningState.java @@ -34,7 +34,6 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.SystemClock; import android.text.format.Formatter; import android.util.Log; import android.util.SparseArray; @@ -54,9 +53,10 @@ public class RunningState { static Object sGlobalLock = new Object(); static RunningState sInstance; - static final int MSG_UPDATE_CONTENTS = 1; - static final int MSG_REFRESH_UI = 2; - static final int MSG_UPDATE_TIME = 3; + static final int MSG_RESET_CONTENTS = 1; + static final int MSG_UPDATE_CONTENTS = 2; + static final int MSG_REFRESH_UI = 3; + static final int MSG_UPDATE_TIME = 4; static final long TIME_UPDATE_DELAY = 1000; static final long CONTENTS_UPDATE_DELAY = 2000; @@ -69,6 +69,8 @@ public class RunningState { OnRefreshUiListener mRefreshUiListener; + final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); + // Processes that are hosting a service we are interested in, organized // by uid and name. Note that this mapping does not change even across // service restarts, and during a restart there will still be a process @@ -133,6 +135,9 @@ public class RunningState { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_RESET_CONTENTS: + reset(); + break; case MSG_UPDATE_CONTENTS: synchronized (mLock) { if (!mResumed) { @@ -561,6 +566,12 @@ public class RunningState { synchronized (mLock) { mResumed = true; mRefreshUiListener = listener; + if (mInterestingConfigChanges.applyNewConfig(mApplicationContext.getResources())) { + mHaveData = false; + mBackgroundHandler.removeMessages(MSG_RESET_CONTENTS); + mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS); + mBackgroundHandler.sendEmptyMessage(MSG_RESET_CONTENTS); + } if (!mBackgroundHandler.hasMessages(MSG_UPDATE_CONTENTS)) { mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS); } @@ -613,6 +624,15 @@ public class RunningState { return false; } + private void reset() { + mServiceProcessesByName.clear(); + mServiceProcessesByPid.clear(); + mInterestingProcesses.clear(); + mRunningProcesses.clear(); + mProcessItems.clear(); + mAllProcessItems.clear(); + } + private boolean update(Context context, ActivityManager am) { final PackageManager pm = context.getPackageManager(); @@ -975,7 +995,7 @@ public class RunningState { } if (newBackgroundItems == null) { - // One or more at the bottom may no longer exit. + // One or more at the bottom may no longer exist. if (mBackgroundItems.size() > numBackgroundProcesses) { newBackgroundItems = new ArrayList<MergedItem>(numBackgroundProcesses); for (int bgi=0; bgi<numBackgroundProcesses; bgi++) { diff --git a/src/com/android/settings/bluetooth/A2dpProfile.java b/src/com/android/settings/bluetooth/A2dpProfile.java new file mode 100644 index 000000000..96225d87c --- /dev/null +++ b/src/com/android/settings/bluetooth/A2dpProfile.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; + +import com.android.settings.R; + +import java.util.List; + +/** + * A2dpProfile handles Bluetooth A2DP. + * TODO: add null checks around calls to mService object. + */ +final class A2dpProfile implements LocalBluetoothProfile { + private BluetoothA2dp mService; + + static final ParcelUuid[] SINK_UUIDS = { + BluetoothUuid.AudioSink, + BluetoothUuid.AdvAudioDist, + }; + + static final String NAME = "A2DP"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 1; + + // These callbacks run on the main thread. + private final class A2dpServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothA2dp) proxy; + } + + public void onServiceDisconnected(int profile) { + mService = null; + } + } + + A2dpProfile(Context context) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter.getProfileProxy(context, new A2dpServiceListener(), + BluetoothProfile.A2DP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + private List<BluetoothDevice> getConnectedDevices() { + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public boolean connect(BluetoothDevice device) { + List<BluetoothDevice> sinks = getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + boolean isA2dpPlaying() { + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (!sinks.isEmpty()) { + if (mService.isA2dpPlaying(sinks.get(0))) { + return true; + } + } + return false; + } + + public boolean isProfileReady() { + return mService != null; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_a2dp; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_a2dp_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_a2dp_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_a2dp_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headphones_a2dp; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothCallback.java b/src/com/android/settings/bluetooth/BluetoothCallback.java new file mode 100644 index 000000000..3ce9adfef --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +/** + * BluetoothCallback provides a callback interface for the settings + * UI to receive events from {@link BluetoothEventManager}. + */ +interface BluetoothCallback { + void onBluetoothStateChanged(int bluetoothState); + void onScanningStateChanged(boolean started); + void onDeviceAdded(CachedBluetoothDevice cachedDevice); + void onDeviceDeleted(CachedBluetoothDevice cachedDevice); + void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState); +} diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java new file mode 100644 index 000000000..00e342ced --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.util.Log; + +/** + * BluetoothDeviceFilter contains a static method that returns a + * Filter object that returns whether or not the BluetoothDevice + * passed to it matches the specified filter type constant from + * {@link android.bluetooth.BluetoothDevicePicker}. + */ +final class BluetoothDeviceFilter { + private static final String TAG = "BluetoothDeviceFilter"; + + /** The filter interface to external classes. */ + interface Filter { + boolean matches(BluetoothDevice device); + } + + /** All filter singleton (referenced directly). */ + static final Filter ALL_FILTER = new AllFilter(); + + /** Bonded devices only filter (referenced directly). */ + static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter(); + + /** Table of singleton filter objects. */ + private static final Filter[] FILTERS = { + ALL_FILTER, // FILTER_TYPE_ALL + new AudioFilter(), // FILTER_TYPE_AUDIO + new TransferFilter(), // FILTER_TYPE_TRANSFER + new PanuFilter(), // FILTER_TYPE_PANU + new NapFilter() // FILTER_TYPE_NAP + }; + + /** Private constructor. */ + private BluetoothDeviceFilter() { + } + + /** + * Returns the singleton {@link Filter} object for the specified type, + * or {@link #ALL_FILTER} if the type value is out of range. + * + * @param filterType a constant from BluetoothDevicePicker + * @return a singleton object implementing the {@link Filter} interface. + */ + static Filter getFilter(int filterType) { + if (filterType >= 0 && filterType < FILTERS.length) { + return FILTERS[filterType]; + } else { + Log.w(TAG, "Invalid filter type " + filterType + " for device picker"); + return ALL_FILTER; + } + } + + /** Filter that matches all devices. */ + private static final class AllFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return true; + } + } + + /** Filter that matches only bonded devices. */ + private static final class BondedDeviceFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return device.getBondState() == BluetoothDevice.BOND_BONDED; + } + } + + /** Parent class of filters based on UUID and/or Bluetooth class. */ + private abstract static class ClassUuidFilter implements Filter { + abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass); + + public boolean matches(BluetoothDevice device) { + return matches(device.getUuids(), device.getBluetoothClass()); + } + } + + /** Filter that matches devices that support AUDIO profiles. */ + private static final class AudioFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) { + return true; + } + if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) { + return true; + } + } else if (btClass != null) { + if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) || + btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return true; + } + } + return false; + } + } + + /** Filter that matches devices that support Object Transfer. */ + private static final class TransferFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP); + } + } + + /** Filter that matches devices that support PAN User (PANU) profile. */ + private static final class PanuFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU); + } + } + + /** Filter that matches devices that support NAP profile. */ + private static final class NapFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP); + } + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java index 6a0b8811c..5f791d942 100644 --- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java @@ -16,27 +16,32 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - +import android.app.AlertDialog; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.DialogInterface; import android.graphics.drawable.Drawable; import android.preference.Preference; +import android.text.TextUtils; +import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.widget.ImageView; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; +import com.android.settings.R; -import java.util.Map; +import java.util.List; /** * BluetoothDevicePreference is the preference type used to display each remote * Bluetooth device in the Bluetooth Settings screen. */ -public class BluetoothDevicePreference extends Preference implements +public final class BluetoothDevicePreference extends Preference implements CachedBluetoothDevice.Callback, OnClickListener { private static final String TAG = "BluetoothDevicePreference"; @@ -48,11 +53,7 @@ public class BluetoothDevicePreference extends Preference implements private OnClickListener mOnSettingsClickListener; - /** - * Cached local copy of whether the device is busy. This is only updated - * from {@link #onDeviceAttributesChanged()}. - */ - private boolean mIsBusy; + private AlertDialog mDisconnectDialog; public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) { super(context); @@ -60,19 +61,19 @@ public class BluetoothDevicePreference extends Preference implements if (sDimAlpha == Integer.MIN_VALUE) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); - sDimAlpha = (int) ((outValue.getFloat() * 255) * 0.5); + sDimAlpha = (int) (outValue.getFloat() * 255); } mCachedDevice = cachedDevice; setWidgetLayoutResource(R.layout.preference_bluetooth); - cachedDevice.registerCallback(this); + mCachedDevice.registerCallback(this); onDeviceAttributesChanged(); } - public CachedBluetoothDevice getCachedDevice() { + CachedBluetoothDevice getCachedDevice() { return mCachedDevice; } @@ -84,42 +85,30 @@ public class BluetoothDevicePreference extends Preference implements protected void onPrepareForRemoval() { super.onPrepareForRemoval(); mCachedDevice.unregisterCallback(this); + if (mDisconnectDialog != null) { + mDisconnectDialog.dismiss(); + mDisconnectDialog = null; + } } public void onDeviceAttributesChanged() { - /* * The preference framework takes care of making sure the value has - * changed before proceeding. + * changed before proceeding. It will also call notifyChanged() if + * any preference info has changed from the previous value. */ - setTitle(mCachedDevice.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(mCachedDevice.getSummary()); + setSummary(getConnectionSummary()); // Used to gray out the item - mIsBusy = mCachedDevice.isBusy(); - - // Data has changed - notifyChanged(); + setEnabled(!mCachedDevice.isBusy()); - // This could affect ordering, so notify that also + // This could affect ordering, so notify that notifyHierarchyChanged(); } @Override - public boolean isEnabled() { - // Temp fix until we have 2053751 fixed in the framework - setEnabled(true); - return super.isEnabled() && !mIsBusy; - } - - @Override protected void onBindView(View view) { // Disable this view if the bluetooth enable/disable preference view is off if (null != findPreferenceInHierarchy("bt_checkbox")) { @@ -129,17 +118,17 @@ public class BluetoothDevicePreference extends Preference implements super.onBindView(view); ImageView btClass = (ImageView) view.findViewById(android.R.id.icon); - btClass.setImageResource(mCachedDevice.getBtClassDrawable()); - btClass.setAlpha(!mIsBusy ? 255 : sDimAlpha); + btClass.setImageResource(getBtClassDrawable()); + btClass.setAlpha(isEnabled() ? 255 : sDimAlpha); mDeviceSettings = (ImageView) view.findViewById(R.id.deviceDetails); if (mOnSettingsClickListener != null) { mDeviceSettings.setOnClickListener(this); mDeviceSettings.setTag(mCachedDevice); - mDeviceSettings.setAlpha(!mIsBusy ? 255 : sDimAlpha); + mDeviceSettings.setAlpha(isEnabled() ? 255 : sDimAlpha); } else { // Hide the settings icon and divider mDeviceSettings.setVisibility(View.GONE); - ImageView divider = (ImageView) view.findViewById(R.id.divider); + View divider = view.findViewById(R.id.divider); if (divider != null) { divider.setVisibility(View.GONE); } @@ -148,24 +137,40 @@ public class BluetoothDevicePreference extends Preference implements LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); ViewGroup profilesGroup = (ViewGroup) view.findViewById(R.id.profileIcons); - Map<Profile, Drawable> profileIcons = mCachedDevice.getProfileIcons(); - for (Profile profile : profileIcons.keySet()) { - Drawable icon = profileIcons.get(profile); - inflater.inflate(R.layout.profile_icon_small, profilesGroup, true); - ImageView imageView = - (ImageView) profilesGroup.getChildAt(profilesGroup.getChildCount() - 1); - imageView.setImageDrawable(icon); - boolean profileEnabled = mCachedDevice.isConnectedProfile(profile); - imageView.setAlpha(profileEnabled ? 255 : sDimAlpha); + for (LocalBluetoothProfile profile : mCachedDevice.getProfiles()) { + int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass()); + if (iconResource != 0) { + Drawable icon = getContext().getResources().getDrawable(iconResource); + inflater.inflate(R.layout.profile_icon_small, profilesGroup, true); + ImageView imageView = + (ImageView) profilesGroup.getChildAt(profilesGroup.getChildCount() - 1); + imageView.setImageDrawable(icon); + boolean profileEnabled = mCachedDevice.isConnectedProfile(profile); + imageView.setAlpha(profileEnabled ? 255 : sDimAlpha); + } } } public void onClick(View v) { if (v == mDeviceSettings) { - if (mOnSettingsClickListener != null) mOnSettingsClickListener.onClick(v); + if (mOnSettingsClickListener != null) { + mOnSettingsClickListener.onClick(v); + } } } + public boolean equals(Object o) { + if ((o == null) || !(o instanceof BluetoothDevicePreference)) { + return false; + } + return mCachedDevice.equals( + ((BluetoothDevicePreference) o).mCachedDevice); + } + + public int hashCode() { + return mCachedDevice.hashCode(); + } + @Override public int compareTo(Preference another) { if (!(another instanceof BluetoothDevicePreference)) { @@ -173,7 +178,112 @@ public class BluetoothDevicePreference extends Preference implements return 1; } - return mCachedDevice.compareTo(((BluetoothDevicePreference) another).mCachedDevice); + return mCachedDevice + .compareTo(((BluetoothDevicePreference) another).mCachedDevice); + } + + void onClicked() { + int bondState = mCachedDevice.getBondState(); + + if (mCachedDevice.isConnected()) { + askDisconnect(); + } else if (bondState == BluetoothDevice.BOND_BONDED) { + mCachedDevice.connect(true); + } else if (bondState == BluetoothDevice.BOND_NONE) { + pair(); + } + } + + // Show disconnect confirmation dialog for a device. + private void askDisconnect() { + Context context = getContext(); + String name = mCachedDevice.getName(); + if (TextUtils.isEmpty(name)) { + name = context.getString(R.string.bluetooth_device); + } + String message = context.getString(R.string.bluetooth_disconnect_blank, name); + + DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mCachedDevice.disconnect(); + } + }; + + mDisconnectDialog = Utils.showDisconnectDialog(context, + mDisconnectDialog, disconnectListener, name, message); + } + + private void pair() { + if (!mCachedDevice.startPairing()) { + Utils.showError(getContext(), mCachedDevice.getName(), + R.string.bluetooth_pairing_error_message); + } + } + + private int getConnectionSummary() { + final CachedBluetoothDevice cachedDevice = mCachedDevice; + final BluetoothDevice device = cachedDevice.getDevice(); + + // if any profiles are connected or busy, return that status + for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) { + int connectionStatus = profile.getConnectionStatus(device); + + if (connectionStatus != BluetoothProfile.STATE_DISCONNECTED) { + return Utils.getConnectionStateSummary(connectionStatus); + } + } + + switch (cachedDevice.getBondState()) { + case BluetoothDevice.BOND_BONDED: + return R.string.bluetooth_paired; + case BluetoothDevice.BOND_BONDING: + return R.string.bluetooth_pairing; + case BluetoothDevice.BOND_NONE: + return R.string.bluetooth_not_connected; + default: + return 0; + } } + private int getBtClassDrawable() { + BluetoothClass btClass = mCachedDevice.getBtClass(); + if (btClass != null) { + switch (btClass.getMajorDeviceClass()) { + case BluetoothClass.Device.Major.COMPUTER: + return R.drawable.ic_bt_laptop; + + case BluetoothClass.Device.Major.PHONE: + return R.drawable.ic_bt_cellphone; + + case BluetoothClass.Device.Major.PERIPHERAL: + return HidProfile.getHidClassDrawable(btClass); + + case BluetoothClass.Device.Major.IMAGING: + return R.drawable.ic_bt_imaging; + + default: + // unrecognized device class; continue + } + } else { + Log.w(TAG, "mBtClass is null"); + } + + List<LocalBluetoothProfile> profiles = mCachedDevice.getProfiles(); + for (LocalBluetoothProfile profile : profiles) { + int resId = profile.getDrawableResource(btClass); + if (resId != 0) { + return resId; + } + } + if (btClass != null) { + if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + return R.drawable.ic_bt_headphones_a2dp; + + } + if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return R.drawable.ic_bt_headset_hfp; + } + } + return 0; + } } diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java index 37e48ff9f..40bf5bc3a 100644 --- a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java @@ -16,39 +16,47 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - import android.bluetooth.BluetoothAdapter; 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.preference.ListPreference; +import android.preference.Preference; + +import com.android.settings.R; /** * 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"; +final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener { private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT = "debug.bt.discoverable_time"; - /* package */ static final int DEFAULT_DISCOVERABLE_TIMEOUT = 120; - /* package */ static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP = - "discoverable_end_timestamp"; + private static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120; + private static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300; + private static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600; + static final int DISCOVERABLE_TIMEOUT_NEVER = 0; + + private static final String VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES = "twomin"; + private static final String VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES = "fivemin"; + private static final String VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR = "onehour"; + private static final String VALUE_DISCOVERABLE_TIMEOUT_NEVER = "never"; + + static final int DEFAULT_DISCOVERABLE_TIMEOUT = DISCOVERABLE_TIMEOUT_TWO_MINUTES; private final Context mContext; private final Handler mUiHandler; private final CheckBoxPreference mCheckBoxPreference; + private final ListPreference mTimeoutListPreference; - private final LocalBluetoothManager mLocalManager; + private final LocalBluetoothAdapter mLocalAdapter; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -69,103 +77,125 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan } }; - public BluetoothDiscoverableEnabler(Context context, CheckBoxPreference checkBoxPreference) { + BluetoothDiscoverableEnabler(Context context, LocalBluetoothAdapter adapter, + CheckBoxPreference checkBoxPreference, ListPreference timeoutListPreference) { mContext = context; mUiHandler = new Handler(); mCheckBoxPreference = checkBoxPreference; + mTimeoutListPreference = timeoutListPreference; checkBoxPreference.setPersistent(false); + // we actually want to persist this since can't infer from BT device state + mTimeoutListPreference.setPersistent(true); - mLocalManager = LocalBluetoothManager.getInstance(context); - if (mLocalManager == null) { + mLocalAdapter = adapter; + if (adapter == null) { // Bluetooth not supported checkBoxPreference.setEnabled(false); } } public void resume() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); mContext.registerReceiver(mReceiver, filter); mCheckBoxPreference.setOnPreferenceChangeListener(this); - - handleModeChanged(mLocalManager.getBluetoothAdapter().getScanMode()); + mTimeoutListPreference.setOnPreferenceChangeListener(this); + handleModeChanged(mLocalAdapter.getScanMode()); } public void pause() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable); mCheckBoxPreference.setOnPreferenceChangeListener(null); + mTimeoutListPreference.setOnPreferenceChangeListener(null); mContext.unregisterReceiver(mReceiver); } public boolean onPreferenceChange(Preference preference, Object value) { - // Turn on/off BT discoverability - setEnabled((Boolean) value); + if (preference == mCheckBoxPreference) { + // Turn on/off BT discoverability + setEnabled((Boolean) value); + } else if (preference == mTimeoutListPreference) { + mTimeoutListPreference.setValue((String) value); + setEnabled(true); + } return true; } - private void setEnabled(final boolean enable) { - BluetoothAdapter manager = mLocalManager.getBluetoothAdapter(); - + private void setEnabled(boolean enable) { if (enable) { - int timeout = getDiscoverableTimeout(); - manager.setDiscoverableTimeout(timeout); + mLocalAdapter.setDiscoverableTimeout(timeout); - mCheckBoxPreference.setSummaryOn( - mContext.getResources().getString(R.string.bluetooth_is_discoverable, timeout)); + long endTimestamp = System.currentTimeMillis() + timeout * 1000L; + LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp); - long endTimestamp = System.currentTimeMillis() + timeout * 1000; - persistDiscoverableEndTimestamp(endTimestamp); + updateCountdownSummary(); - manager.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout); } else { - manager.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + } + } + + private void updateTimerDisplay(int timeout) { + if (getDiscoverableTimeout() == DISCOVERABLE_TIMEOUT_NEVER) { + mCheckBoxPreference.setSummaryOn( + mContext.getString(R.string.bluetooth_is_discoverable_always)); + } else { + mCheckBoxPreference.setSummaryOn( + mContext.getString(R.string.bluetooth_is_discoverable, timeout)); } } private int getDiscoverableTimeout() { int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1); - if (timeout <= 0) { - timeout = DEFAULT_DISCOVERABLE_TIMEOUT; + if (timeout < 0) { + String timeoutValue = mTimeoutListPreference.getValue(); + if (timeoutValue == null) { + mTimeoutListPreference.setValue(VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES); + return DISCOVERABLE_TIMEOUT_TWO_MINUTES; + } + + if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_NEVER)) { + timeout = DISCOVERABLE_TIMEOUT_NEVER; + } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR)) { + timeout = DISCOVERABLE_TIMEOUT_ONE_HOUR; + } else if (timeoutValue.equals(VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES)) { + timeout = DISCOVERABLE_TIMEOUT_FIVE_MINUTES; + } else { + timeout = DISCOVERABLE_TIMEOUT_TWO_MINUTES; + } } return timeout; } - private void persistDiscoverableEndTimestamp(long endTimestamp) { - SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit(); - editor.putLong(SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp); - editor.apply(); - } - private void handleModeChanged(int mode) { if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { mCheckBoxPreference.setChecked(true); updateCountdownSummary(); - } else { mCheckBoxPreference.setChecked(false); } } private void updateCountdownSummary() { - int mode = mLocalManager.getBluetoothAdapter().getScanMode(); + int mode = mLocalAdapter.getScanMode(); if (mode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { return; } long currentTimestamp = System.currentTimeMillis(); - long endTimestamp = mLocalManager.getSharedPreferences().getLong( - SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0); + long endTimestamp = LocalBluetoothPreferences.getDiscoverableEndTimestamp(mContext); if (currentTimestamp > endTimestamp) { // We're still in discoverable mode, but maybe there isn't a timeout. @@ -173,17 +203,12 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan return; } - String formattedTimeLeft = String.valueOf((endTimestamp - currentTimestamp) / 1000); - - mCheckBoxPreference.setSummaryOn( - mContext.getResources().getString(R.string.bluetooth_is_discoverable, - formattedTimeLeft)); + int timeLeft = (int) ((endTimestamp - currentTimestamp) / 1000L); + updateTimerDisplay(timeLeft); synchronized (this) { mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable); mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000); } } - - } diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java b/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java new file mode 100644 index 000000000..fbb682756 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +/** + * BluetoothDiscoveryReceiver updates a timestamp when the + * Bluetooth adapter starts or finishes discovery mode. This + * is used to decide whether to open an alert dialog or + * create a notification when we receive a pairing request. + * + * <p>Note that the discovery start/finish intents are also handled + * by {@link BluetoothEventManager} to update the UI, if visible. + */ +public final class BluetoothDiscoveryReceiver extends BroadcastReceiver { + private static final String TAG = "BluetoothDiscoveryReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.v(TAG, "Received: " + action); + + if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED) || + action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { + LocalBluetoothPreferences.persistDiscoveringTimestamp(context); + } + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index a7b9d713e..9aeb1b939 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -34,13 +34,14 @@ import android.widget.Toast; * preference. It turns on/off Bluetooth and ensures the summary of the * preference reflects the current state. */ -public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { +public final class BluetoothEnabler implements Preference.OnPreferenceChangeListener { private final Context mContext; private final CheckBoxPreference mCheckBox; private final CharSequence mOriginalSummary; - private final LocalBluetoothManager mLocalManager; + private final LocalBluetoothAdapter mLocalAdapter; private final IntentFilter mIntentFilter; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -49,14 +50,15 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { } }; - public BluetoothEnabler(Context context, CheckBoxPreference checkBox) { + public BluetoothEnabler(Context context, LocalBluetoothAdapter adapter, + CheckBoxPreference checkBox) { mContext = context; mCheckBox = checkBox; mOriginalSummary = checkBox.getSummary(); checkBox.setPersistent(false); - mLocalManager = LocalBluetoothManager.getInstance(context); - if (mLocalManager == null) { + mLocalAdapter = adapter; + if (adapter == null) { // Bluetooth is not supported checkBox.setEnabled(false); } @@ -64,19 +66,19 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { } public void resume() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } // Bluetooth state is not sticky, so set it manually - handleStateChanged(mLocalManager.getBluetoothState()); + handleStateChanged(mLocalAdapter.getBluetoothState()); mContext.registerReceiver(mReceiver, mIntentFilter); mCheckBox.setOnPreferenceChangeListener(this); } public void pause() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } @@ -95,14 +97,14 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { return false; } - mLocalManager.setBluetoothEnabled(enable); + mLocalAdapter.setBluetoothEnabled(enable); mCheckBox.setEnabled(false); // Don't update UI to opposite state until we're sure return false; } - /* package */ void handleStateChanged(int state) { + void handleStateChanged(int state) { switch (state) { case BluetoothAdapter.STATE_TURNING_ON: mCheckBox.setSummary(R.string.wifi_starting); diff --git a/src/com/android/settings/bluetooth/BluetoothEventManager.java b/src/com/android/settings/bluetooth/BluetoothEventManager.java new file mode 100644 index 000000000..70e35f984 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothEventManager.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import com.android.settings.R; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth + * API and dispatches the event on the UI thread to the right class in the + * Settings. + */ +final class BluetoothEventManager { + private static final String TAG = "BluetoothEventManager"; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private LocalBluetoothProfileManager mProfileManager; + private final IntentFilter mIntentFilter; + private final Map<String, Handler> mHandlerMap; + + private final Collection<BluetoothCallback> mCallbacks = + new ArrayList<BluetoothCallback>(); + + interface Handler { + void onReceive(Context context, Intent intent, BluetoothDevice device); + } + + void addHandler(String action, Handler handler) { + mHandlerMap.put(action, handler); + mIntentFilter.addAction(action); + } + + // Set profile manager after construction due to circular dependency + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + BluetoothEventManager(LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mIntentFilter = new IntentFilter(); + mHandlerMap = new HashMap<String, Handler>(); + + // Bluetooth on/off broadcasts + addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler()); + + // Discovery broadcasts + addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); + addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); + addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); + addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); + addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); + + // Pairing broadcasts + addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler()); + addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler()); + + // Fine-grained state broadcasts + addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler()); + addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); + + // Dock event broadcasts + addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler()); + } + + /** + * A Bluetooth-related activity is now in the foreground. Register to + * start receiving Bluetooth events. + * @param context a Context object for the current Activity + */ + void resume(Context context) { + if (mLocalAdapter.syncBluetoothState()) { + // adapter state changed while we were paused: send callbacks + int newState = mLocalAdapter.getState(); + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onBluetoothStateChanged(newState); + } + } + } + context.registerReceiver(mBroadcastReceiver, mIntentFilter); + } + + void pause(Context context) { + context.unregisterReceiver(mBroadcastReceiver); + } + + /** Register to start receiving callbacks for Bluetooth events. */ + void registerCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + /** Unregister to stop receiving callbacks for Bluetooth events. */ + void unregisterCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + // This can't be called from a broadcast receiver where the filter is set in the Manifest. + private static String getDockedDeviceAddress(Context context) { + // This works only because these broadcast intents are "sticky" + Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (i != null) { + int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device != null) { + return device.getAddress(); + } + } + } + return null; + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.v(TAG, "Received " + intent.getAction()); + + String action = intent.getAction(); + BluetoothDevice device = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + Handler handler = mHandlerMap.get(action); + if (handler != null) { + handler.onReceive(context, intent, device); + } + } + }; + + private class AdapterStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + // update local profiles and get paired devices + mLocalAdapter.setBluetoothStateInt(state); + // send callback to update UI and possibly start scanning + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onBluetoothStateChanged(state); + } + } + } + } + + private class ScanningStateChangedHandler implements Handler { + private final boolean mStarted; + + ScanningStateChangedHandler(boolean started) { + mStarted = started; + } + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onScanningStateChanged(mStarted); + } + } + mDeviceManager.onScanningStateChanged(mStarted); + LocalBluetoothPreferences.persistDiscoveringTimestamp(context); + } + } + + private class DeviceFoundHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); + BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); + String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); + // TODO Pick up UUID. They should be available for 2.1 devices. + // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " + + cachedDevice); + // callback to UI to create Preference for new device + dispatchDeviceAdded(cachedDevice); + } + cachedDevice.setRssi(rssi); + cachedDevice.setBtClass(btClass); + cachedDevice.setName(name); + cachedDevice.setVisible(true); + } + } + + private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceAdded(cachedDevice); + } + } + } + + private class DeviceDisappearedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); + return; + } + if (mDeviceManager.onDeviceDisappeared(cachedDevice)) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceDeleted(cachedDevice); + } + } + } + } + } + + private class NameChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onDeviceNameUpdated(device); + } + } + + private class BondStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + return; + } + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "CachedBluetoothDevice for device " + device + + " not found, calling readPairedDevices()."); + if (!readPairedDevices()) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but we have no record of that device."); + return; + } + cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but device not added in cache."); + return; + } + } + + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceBondStateChanged(cachedDevice, bondState); + } + } + cachedDevice.onBondingStateChanged(bondState); + + if (bondState == BluetoothDevice.BOND_NONE) { + if (device.isBluetoothDock()) { + // After a dock is unpaired, we will forget the settings + LocalBluetoothPreferences + .removeDockAutoConnectSetting(context, device.getAddress()); + + // if the device is undocked, remove it from the list as well + if (!device.getAddress().equals(getDockedDeviceAddress(context))) { + mDeviceManager.onDeviceDisappeared(cachedDevice); + } + } + int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, + BluetoothDevice.ERROR); + + showUnbondMessage(context, cachedDevice.getName(), reason); + } + } + + /** + * Called when we have reached the unbonded state. + * + * @param reason one of the error reasons from + * BluetoothDevice.UNBOND_REASON_* + */ + private void showUnbondMessage(Context context, String name, int reason) { + int errorMsg; + + switch(reason) { + case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: + errorMsg = R.string.bluetooth_pairing_pin_error_message; + break; + case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: + errorMsg = R.string.bluetooth_pairing_rejected_error_message; + break; + case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: + errorMsg = R.string.bluetooth_pairing_device_down_error_message; + break; + case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: + case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: + case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: + case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: + errorMsg = R.string.bluetooth_pairing_error_message; + break; + default: + Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason); + return; + } + Utils.showError(context, name, errorMsg); + } + } + + private class ClassChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onBtClassChanged(device); + } + } + + private class UuidChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onUuidChanged(device); + } + } + + private class PairingCancelHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE"); + return; + } + int errorMsg = R.string.bluetooth_pairing_error_message; + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + Utils.showError(context, cachedDevice.getName(), errorMsg); + } + } + + private class DockEventHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + // Remove if unpair device upon undocking + int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); + if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice != null) { + mDeviceManager.onDeviceDisappeared(cachedDevice); + } + } + } + } + } + + boolean readPairedDevices() { + Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices(); + if (bondedDevices == null) { + return false; + } + + boolean deviceAdded = false; + for (BluetoothDevice device : bondedDevices) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + dispatchDeviceAdded(cachedDevice); + deviceAdded = true; + } + } + + return deviceAdded; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java deleted file mode 100644 index 3023dafb7..000000000 --- a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.bluetooth; - -import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothInputDevice; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -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.util.Log; - -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * BluetoothEventRedirector receives broadcasts and callbacks from the Bluetooth - * API and dispatches the event on the UI thread to the right class in the - * Settings. - */ -class BluetoothEventRedirector { - private static final String TAG = "BluetoothEventRedirector"; - - /* package */ final LocalBluetoothManager mManager; - - private final ThreadPoolExecutor mSerialExecutor = new ThreadPoolExecutor( - 0, 1, 1000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); - - private final Handler mHandler = new Handler(); - - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Log.v(TAG, "Received " + intent.getAction()); - - String action = intent.getAction(); - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - mManager.setBluetoothStateInt(state); - } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) { - PendingResult pr = goAsync(); // so loading shared prefs doesn't kill animation - persistDiscoveringTimestamp(pr, true); - } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { - PendingResult pr = goAsync(); // so loading shared prefs doesn't kill animation - persistDiscoveringTimestamp(pr, false); - } else if (action.equals(BluetoothDevice.ACTION_FOUND)) { - short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); - BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); - String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); - // TODO Pick up UUID. They should be available for 2.1 devices. - // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. - mManager.getCachedDeviceManager().onDeviceAppeared(device, rssi, btClass, name); - - } else if (action.equals(BluetoothDevice.ACTION_DISAPPEARED)) { - mManager.getCachedDeviceManager().onDeviceDisappeared(device); - - } else if (action.equals(BluetoothDevice.ACTION_NAME_CHANGED)) { - mManager.getCachedDeviceManager().onDeviceNameUpdated(device); - - } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { - int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, - BluetoothDevice.ERROR); - CachedBluetoothDeviceManager cachedDeviceMgr = mManager.getCachedDeviceManager(); - cachedDeviceMgr.onBondingStateChanged(device, bondState); - if (bondState == BluetoothDevice.BOND_NONE) { - if (device.isBluetoothDock()) { - // After a dock is unpaired, we will forget the - // settings - mManager.removeDockAutoConnectSetting(device.getAddress()); - - // if the device is undocked, remove it from the list as - // well - if (!device.getAddress().equals(getDockedDeviceAddress(context))) { - cachedDeviceMgr.onDeviceDisappeared(device); - } - } - int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, - BluetoothDevice.ERROR); - cachedDeviceMgr.showUnbondMessage(device, reason); - } - - } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - if (newState == BluetoothProfile.STATE_DISCONNECTED && - oldState == BluetoothProfile.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT headset"); - } - - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.HEADSET, newState); - } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - if (newState == BluetoothProfile.STATE_DISCONNECTED && - oldState == BluetoothProfile.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT A2DP"); - } - - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.A2DP, newState); - } else if (action.equals(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED)) { - final int newState = intent.getIntExtra( - BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, 0); - final int oldState = intent.getIntExtra( - BluetoothInputDevice.EXTRA_PREVIOUS_INPUT_DEVICE_STATE, 0); - if (newState == BluetoothInputDevice.STATE_DISCONNECTED && - oldState == BluetoothInputDevice.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT HID"); - } - - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.HID, newState); - - } else if (action.equals(BluetoothPan.ACTION_PAN_STATE_CHANGED)) { - final int role = intent.getIntExtra( - BluetoothPan.EXTRA_LOCAL_ROLE, 0); - if (role == BluetoothPan.LOCAL_PANU_ROLE) { - final int newState = intent.getIntExtra( - BluetoothPan.EXTRA_PAN_STATE, 0); - final int oldState = intent.getIntExtra( - BluetoothPan.EXTRA_PREVIOUS_PAN_STATE, 0); - if (newState == BluetoothPan.STATE_DISCONNECTED && - oldState == BluetoothPan.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT PAN"); - } - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.PAN, newState); - } - } else if (action.equals(BluetoothDevice.ACTION_CLASS_CHANGED)) { - mManager.getCachedDeviceManager().onBtClassChanged(device); - - } else if (action.equals(BluetoothDevice.ACTION_UUID)) { - mManager.getCachedDeviceManager().onUuidChanged(device); - - } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) { - int errorMsg = R.string.bluetooth_pairing_error_message; - mManager.showError(device, errorMsg); - - } else if (action.equals(Intent.ACTION_DOCK_EVENT)) { - // Remove if unpair device upon undocking - int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; - int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); - if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { - mManager.getCachedDeviceManager().onDeviceDisappeared(device); - } - } - } - } - }; - - public BluetoothEventRedirector(LocalBluetoothManager localBluetoothManager) { - mManager = localBluetoothManager; - } - - public void registerReceiver() { - IntentFilter filter = new IntentFilter(); - - // Bluetooth on/off broadcasts - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - - // Discovery broadcasts - filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); - filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); - filter.addAction(BluetoothDevice.ACTION_DISAPPEARED); - filter.addAction(BluetoothDevice.ACTION_FOUND); - filter.addAction(BluetoothDevice.ACTION_NAME_CHANGED); - - // Pairing broadcasts - filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL); - - // Fine-grained state broadcasts - filter.addAction(BluetoothPan.ACTION_PAN_STATE_CHANGED); - filter.addAction(BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED); - filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_CLASS_CHANGED); - filter.addAction(BluetoothDevice.ACTION_UUID); - - // Dock event broadcasts - filter.addAction(Intent.ACTION_DOCK_EVENT); - - mManager.getContext().registerReceiver(mBroadcastReceiver, filter); - } - - public void stop() { - mManager.getContext().unregisterReceiver(mBroadcastReceiver); - } - - // This can't be called from a broadcast receiver where the filter is set in the Manifest. - /* package */ String getDockedDeviceAddress(Context context) { - // This works only because these broadcast intents are "sticky" - Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device != null) { - return device.getAddress(); - } - } - } - return null; - } - - /* package */ void persistDiscoveringTimestamp( - final BroadcastReceiver.PendingResult pr, final boolean newState) { - // Load the shared preferences and edit it on a background - // thread (but serialized!), but then post back to the main - // thread to run the onScanningStateChanged callbacks which - // update the UI... - mSerialExecutor.submit(new Runnable() { - public void run() { - SharedPreferences.Editor editor = mManager.getSharedPreferences().edit(); - editor.putLong( - LocalBluetoothManager.SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, - System.currentTimeMillis()); - editor.apply(); - mHandler.post(new Runnable() { - public void run() { - mManager.onScanningStateChanged(newState); - pr.finish(); - } - }); - } - }); - } -} diff --git a/src/com/android/settings/bluetooth/BluetoothFindNearby.java b/src/com/android/settings/bluetooth/BluetoothFindNearby.java index f1b876e76..066f4f611 100644 --- a/src/com/android/settings/bluetooth/BluetoothFindNearby.java +++ b/src/com/android/settings/bluetooth/BluetoothFindNearby.java @@ -16,21 +16,18 @@ package com.android.settings.bluetooth; -import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.util.Log; import com.android.settings.R; /** * Fragment to scan and show the discoverable devices. */ -public class BluetoothFindNearby extends DeviceListPreferenceFragment { +public final class BluetoothFindNearby extends DeviceListPreferenceFragment { - private static final String TAG = "BluetoothFindNearby"; - - void addPreferencesForActivity(Activity activity) { + @Override + void addPreferencesForActivity() { addPreferencesFromResource(R.xml.device_picker); } @@ -38,35 +35,37 @@ public class BluetoothFindNearby extends DeviceListPreferenceFragment { public void onResume() { super.onResume(); if (mSelectedDevice != null) { - CachedBluetoothDevice device = - mLocalManager.getCachedDeviceManager().findDevice(mSelectedDevice); - if (device.getBondState() == BluetoothDevice.BOND_BONDED) { + CachedBluetoothDeviceManager manager = mLocalManager.getCachedDeviceManager(); + CachedBluetoothDevice device = manager.findDevice(mSelectedDevice); + if (device != null && device.getBondState() == BluetoothDevice.BOND_BONDED) { // selected device was paired, so return from this screen finish(); return; } } - mLocalManager.startScanning(true); + mLocalAdapter.startScanning(true); } + @Override void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - mLocalManager.stopScanning(); + mLocalAdapter.stopScanning(); super.onDevicePreferenceClick(btPreference); } - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, - int bondState) { + public void onDeviceBondStateChanged(CachedBluetoothDevice + cachedDevice, int bondState) { if (bondState == BluetoothDevice.BOND_BONDED) { // return from scan screen after successful auto-pairing finish(); } } - void onBluetoothStateChanged(int bluetoothState) { + @Override + public void onBluetoothStateChanged(int bluetoothState) { super.onBluetoothStateChanged(bluetoothState); if (bluetoothState == BluetoothAdapter.STATE_ON) { - mLocalManager.startScanning(false); + mLocalAdapter.startScanning(false); } } } diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java index c99ab4c99..f41689ef7 100644 --- a/src/com/android/settings/bluetooth/BluetoothNamePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothNamePreference.java @@ -22,12 +22,12 @@ import android.app.Dialog; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.preference.EditTextPreference; import android.text.Editable; import android.text.InputFilter; -import android.text.Spanned; import android.text.TextWatcher; import android.util.AttributeSet; import android.widget.Button; @@ -38,13 +38,13 @@ import android.widget.EditText; * Bluetooth name. It asks the user for a name, and persists it via the * Bluetooth API. */ -public class BluetoothNamePreference extends EditTextPreference implements TextWatcher { - private static final String TAG = "BluetoothNamePreference"; +public final class BluetoothNamePreference extends EditTextPreference implements TextWatcher { +// private static final String TAG = "BluetoothNamePreference"; private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248; - private LocalBluetoothManager mLocalManager; + private final LocalBluetoothAdapter mLocalAdapter; - private BroadcastReceiver mReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -61,7 +61,7 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW public BluetoothNamePreference(Context context, AttributeSet attrs) { super(context, attrs); - mLocalManager = LocalBluetoothManager.getInstance(context); + mLocalAdapter = LocalBluetoothManager.getInstance(context).getBluetoothAdapter(); setSummaryToName(); } @@ -97,16 +97,17 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW } private void setSummaryToName() { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - if (adapter.isEnabled()) { - setSummary(adapter.getName()); + if (mLocalAdapter != null && mLocalAdapter.isEnabled()) { + setSummary(mLocalAdapter.getName()); } } @Override protected boolean persistString(String value) { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - adapter.setName(value); + // Persist with Bluez instead of shared preferences + if (mLocalAdapter != null) { + mLocalAdapter.setName(value); + } return true; } @@ -116,8 +117,8 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW // The dialog should be created by now EditText et = getEditText(); - if (et != null) { - et.setText(mLocalManager.getBluetoothAdapter().getName()); + if (et != null && mLocalAdapter != null) { + et.setText(mLocalAdapter.getName()); } } @@ -125,7 +126,7 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW public void afterTextChanged(Editable s) { Dialog d = getDialog(); if (d instanceof AlertDialog) { - ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0); + ((AlertDialog) d).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(s.length() > 0); } } @@ -139,67 +140,4 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW // not used } - /** - * This filter will constrain edits so that the text length is not - * greater than the specified number of bytes using UTF-8 encoding. - * <p>The JNI method used by {@link android.server.BluetoothService} - * to convert UTF-16 to UTF-8 doesn't support surrogate pairs, - * therefore code points outside of the basic multilingual plane - * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters, - * rather than a single 4-byte UTF-8 encoding. Dalvik implements this - * conversion in {@code convertUtf16ToUtf8()} in - * {@code dalvik/vm/UtfString.c}. - * <p>This JNI method is unlikely to change in the future due to - * backwards compatibility requirements. It's also unclear whether - * the installed base of Bluetooth devices would correctly handle the - * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6. - * However, this filter will still work in scenarios where surrogate - * pairs are encoded as 4 bytes, with the caveat that the maximum - * length will be constrained more conservatively than necessary. - */ - public static class Utf8ByteLengthFilter implements InputFilter { - private int mMaxBytes; - - public Utf8ByteLengthFilter(int maxBytes) { - mMaxBytes = maxBytes; - } - - public CharSequence filter(CharSequence source, int start, int end, - Spanned dest, int dstart, int dend) { - int srcByteCount = 0; - // count UTF-8 bytes in source substring - for (int i = start; i < end; i++) { - char c = source.charAt(i); - srcByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); - } - int destLen = dest.length(); - int destByteCount = 0; - // count UTF-8 bytes in destination excluding replaced section - for (int i = 0; i < destLen; i++) { - if (i < dstart || i >= dend) { - char c = dest.charAt(i); - destByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); - } - } - int keepBytes = mMaxBytes - destByteCount; - if (keepBytes <= 0) { - return ""; - } else if (keepBytes >= srcByteCount) { - return null; // use original dest string - } else { - // find end position of largest sequence that fits in keepBytes - for (int i = start; i < end; i++) { - char c = source.charAt(i); - keepBytes -= (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); - if (keepBytes < 0) { - return source.subSequence(start, i); - } - } - // If the entire substring fits, we should have returned null - // above, so this line should not be reached. If for some - // reason it is, return null to use the original dest string. - return null; - } - } - } } diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java index 1822e73f8..1b443c497 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java @@ -42,33 +42,37 @@ import com.android.settings.R; * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog. */ -public class BluetoothPairingDialog extends AlertActivity implements DialogInterface.OnClickListener, +public final class BluetoothPairingDialog extends AlertActivity implements DialogInterface.OnClickListener, TextWatcher { private static final String TAG = "BluetoothPairingDialog"; private static final int BLUETOOTH_PIN_MAX_LENGTH = 16; private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6; - private LocalBluetoothManager mLocalManager; private BluetoothDevice mDevice; private int mType; private String mPairingKey; private EditText mPairingView; private Button mOkButton; - private BroadcastReceiver mReceiver = new BroadcastReceiver() { + /** + * Dismiss the dialog if the bond state changes to bonded or none, + * or if pairing was canceled for {@link #mDevice}. + */ + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + String action = intent.getAction(); + if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); if (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_NONE) { - dismissDialog(); + dismiss(); } - } else if(BluetoothDevice.ACTION_PAIRING_CANCEL.equals(intent.getAction())) { + } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device == null || device.equals(mDevice)) { - dismissDialog(); + dismiss(); } } } @@ -81,48 +85,63 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter Intent intent = getIntent(); if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { - Log.e(TAG, - "Error: this activity may be started only with intent " + + Log.e(TAG, "Error: this activity may be started only with intent " + BluetoothDevice.ACTION_PAIRING_REQUEST); finish(); + return; } - mLocalManager = LocalBluetoothManager.getInstance(this); + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Error: BluetoothAdapter not supported by system"); + finish(); + return; + } + CachedBluetoothDeviceManager deviceManager = manager.getCachedDeviceManager(); + mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); - if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) { - createUserEntryDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY) { - createUserEntryDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION){ - int passkey = - intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); - if (passkey == BluetoothDevice.ERROR) { - Log.e(TAG, "Invalid ConfirmationPasskey received, not showing any dialog"); - return; - } - mPairingKey = String.format("%06d", passkey); - createConfirmationDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) { - createConsentDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY || - mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { - int pairingKey = - intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); - if (pairingKey == BluetoothDevice.ERROR) { - Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog"); - return; - } - if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { - mPairingKey = String.format("%06d", pairingKey); - } else { - mPairingKey = String.format("%04d", pairingKey); - } - createDisplayPasskeyOrPinDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) { - createConsentDialog(); - } else { - Log.e(TAG, "Incorrect pairing type received, not showing any dialog"); + + switch (mType) { + case BluetoothDevice.PAIRING_VARIANT_PIN: + case BluetoothDevice.PAIRING_VARIANT_PASSKEY: + createUserEntryDialog(deviceManager); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: + int passkey = + intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); + if (passkey == BluetoothDevice.ERROR) { + Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog"); + return; + } + mPairingKey = String.format("%06d", passkey); + createConfirmationDialog(deviceManager); + break; + + case BluetoothDevice.PAIRING_VARIANT_CONSENT: + case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: + createConsentDialog(deviceManager); + break; + + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: + int pairingKey = + intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); + if (pairingKey == BluetoothDevice.ERROR) { + Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog"); + return; + } + if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { + mPairingKey = String.format("%06d", pairingKey); + } else { + mPairingKey = String.format("%04d", pairingKey); + } + createDisplayPasskeyOrPinDialog(deviceManager); + break; + + default: + Log.e(TAG, "Incorrect pairing type received, not showing any dialog"); } /* @@ -133,67 +152,79 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)); } - private void createUserEntryDialog() { + private void createUserEntryDialog(CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); p.mPositiveButtonText = getString(android.R.string.ok); p.mPositiveButtonListener = this; p.mNegativeButtonText = getString(android.R.string.cancel); p.mNegativeButtonListener = this; setupAlert(); - mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); + mOkButton = mAlert.getButton(BUTTON_POSITIVE); mOkButton.setEnabled(false); } - private View createView() { + private View createView(CachedBluetoothDeviceManager deviceManager) { View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null); - - String name = mLocalManager.getCachedDeviceManager().getName(mDevice); + String name = deviceManager.getName(mDevice); TextView messageView = (TextView) view.findViewById(R.id.message); mPairingView = (EditText) view.findViewById(R.id.text); mPairingView.addTextChangedListener(this); - if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) { - messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name)); - // Maximum of 16 characters in a PIN adb sync - mPairingView.setFilters(new InputFilter[] { - new LengthFilter(BLUETOOTH_PIN_MAX_LENGTH) }); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY){ - messageView.setText(getString(R.string.bluetooth_enter_passkey_msg, name)); - // Maximum of 6 digits for passkey - mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER | - InputType.TYPE_NUMBER_FLAG_SIGNED); - mPairingView.setFilters(new InputFilter[] { - new LengthFilter(BLUETOOTH_PASSKEY_MAX_LENGTH)}); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_confirm_passkey_msg, name, - mPairingKey)); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY || - mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_display_passkey_pin_msg, name, - mPairingKey)); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); - } else { - Log.e(TAG, "Incorrect pairing type received, not creating view"); + switch (mType) { + case BluetoothDevice.PAIRING_VARIANT_PIN: + messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name)); + // Maximum of 16 characters in a PIN adb sync + mPairingView.setFilters(new InputFilter[] { + new LengthFilter(BLUETOOTH_PIN_MAX_LENGTH) }); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY: + messageView.setText(getString(R.string.bluetooth_enter_passkey_msg, name)); + // Maximum of 6 digits for passkey + mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER | + InputType.TYPE_NUMBER_FLAG_SIGNED); + mPairingView.setFilters(new InputFilter[] { + new LengthFilter(BLUETOOTH_PASSKEY_MAX_LENGTH)}); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_confirm_passkey_msg, name, + mPairingKey)); + break; + + case BluetoothDevice.PAIRING_VARIANT_CONSENT: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); + break; + + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_display_passkey_pin_msg, name, + mPairingKey)); + break; + + case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); + break; + + default: + Log.e(TAG, "Incorrect pairing type received, not creating view"); } return view; } - private void createConfirmationDialog() { + private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept); p.mPositiveButtonListener = this; p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline); @@ -201,11 +232,11 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter setupAlert(); } - private void createConsentDialog() { + private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept); p.mPositiveButtonListener = this; p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline); @@ -213,11 +244,12 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter setupAlert(); } - private void createDisplayPasskeyOrPinDialog() { + private void createDisplayPasskeyOrPinDialog( + CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); p.mNegativeButtonText = getString(android.R.string.cancel); p.mNegativeButtonListener = this; setupAlert(); @@ -244,32 +276,37 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter } } - private void dismissDialog() { - this.dismiss(); - } - private void onPair(String value) { - if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) { - byte[] pinBytes = BluetoothDevice.convertPinToBytes(value); - if (pinBytes == null) { - return; - } - mDevice.setPin(pinBytes); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY) { - int passkey = Integer.parseInt(value); - mDevice.setPasskey(passkey); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) { - mDevice.setPairingConfirmation(true); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) { - mDevice.setPairingConfirmation(true); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { - // Do Nothing. - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { - // Do Nothing - } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) { - mDevice.setRemoteOutOfBandData(); - } else { - Log.e(TAG, "Incorrect pairing type received"); + switch (mType) { + case BluetoothDevice.PAIRING_VARIANT_PIN: + byte[] pinBytes = BluetoothDevice.convertPinToBytes(value); + if (pinBytes == null) { + return; + } + mDevice.setPin(pinBytes); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY: + int passkey = Integer.parseInt(value); + mDevice.setPasskey(passkey); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: + case BluetoothDevice.PAIRING_VARIANT_CONSENT: + mDevice.setPairingConfirmation(true); + break; + + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: + // Do nothing. + break; + + case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: + mDevice.setRemoteOutOfBandData(); + break; + + default: + Log.e(TAG, "Incorrect pairing type received"); } } @@ -279,11 +316,12 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter public void onClick(DialogInterface dialog, int which) { switch (which) { - case DialogInterface.BUTTON_POSITIVE: + case BUTTON_POSITIVE: onPair(mPairingView.getText().toString()); break; - case DialogInterface.BUTTON_NEGATIVE: + case BUTTON_NEGATIVE: + default: onCancel(); break; } diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java index 6037c82a9..de96d7119 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java @@ -35,17 +35,15 @@ import android.os.PowerManager; * confirmation entry dialog. Otherwise it puts a Notification in the status bar, which can * be clicked to bring up the Pairing entry dialog. */ -public class BluetoothPairingRequest extends BroadcastReceiver { +public final class BluetoothPairingRequest extends BroadcastReceiver { - public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; + private 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(BluetoothDevice.ACTION_PAIRING_REQUEST)) { - - LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(context); - + // convert broadcast intent into activity intent (same action string) BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, @@ -68,39 +66,35 @@ public class BluetoothPairingRequest extends BroadcastReceiver { (PowerManager)context.getSystemService(Context.POWER_SERVICE); String deviceAddress = device != null ? device.getAddress() : null; if (powerManager.isScreenOn() && - localManager.shouldShowDialogInForeground(deviceAddress)) { + LocalBluetoothPreferences.shouldShowDialogInForeground(context, deviceAddress)) { // Since the screen is on and the BT-related activity is in the foreground, // just open the dialog context.startActivity(pairingIntent); - } 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()); + Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) + .setTicker(res.getString(R.string.bluetooth_notif_ticker)); PendingIntent pending = PendingIntent.getActivity(context, 0, pairingIntent, PendingIntent.FLAG_ONE_SHOT); String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); if (TextUtils.isEmpty(name)) { - name = (device != null ? device.getName() : - context.getString(android.R.string.unknownName)); + name = device != null ? device.getName() : + context.getString(android.R.string.unknownName); } - 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; - notification.defaults |= Notification.DEFAULT_SOUND; + builder.setContentTitle(res.getString(R.string.bluetooth_notif_title)) + .setContentText(res.getString(R.string.bluetooth_notif_message, name)) + .setContentIntent(pending) + .setAutoCancel(true) + .setDefaults(Notification.DEFAULT_SOUND); NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - manager.notify(NOTIFICATION_ID, notification); + manager.notify(NOTIFICATION_ID, builder.getNotification()); } } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) { diff --git a/src/com/android/settings/bluetooth/BluetoothProfilePreference.java b/src/com/android/settings/bluetooth/BluetoothProfilePreference.java index c74012a20..e334867ec 100644 --- a/src/com/android/settings/bluetooth/BluetoothProfilePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothProfilePreference.java @@ -16,9 +16,6 @@ package com.android.settings.bluetooth; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; -import com.android.settings.R; - import android.content.Context; import android.graphics.drawable.Drawable; import android.preference.Preference; @@ -26,22 +23,24 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; +import com.android.settings.R; + /** * BluetoothProfilePreference is the preference type used to display each profile for a * particular bluetooth device. */ -public class BluetoothProfilePreference extends Preference implements OnClickListener { +final class BluetoothProfilePreference extends Preference implements OnClickListener { - private static final String TAG = "BluetoothProfilePreference"; +// private static final String TAG = "BluetoothProfilePreference"; private Drawable mProfileDrawable; private boolean mExpanded; private ImageView mProfileExpandView; - private final Profile mProfile; + private final LocalBluetoothProfile mProfile; private OnClickListener mOnExpandClickListener; - public BluetoothProfilePreference(Context context, Profile profile) { + BluetoothProfilePreference(Context context, LocalBluetoothProfile profile) { super(context); mProfile = profile; @@ -75,14 +74,14 @@ public class BluetoothProfilePreference extends Preference implements OnClickLis btProfile.setImageDrawable(mProfileDrawable); mProfileExpandView = (ImageView) view.findViewById(R.id.profileExpand); - if (mProfile == Profile.PAN) { - mProfileExpandView.setVisibility(View.GONE); - } else { + if (mProfile.isAutoConnectable()) { mProfileExpandView.setOnClickListener(this); mProfileExpandView.setTag(mProfile); mProfileExpandView.setImageResource(mExpanded ? com.android.internal.R.drawable.expander_open_holo_dark : com.android.internal.R.drawable.expander_close_holo_dark); + } else { + mProfileExpandView.setVisibility(View.GONE); } } diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java index c1fda6bfc..c6ba9af56 100644 --- a/src/com/android/settings/bluetooth/BluetoothSettings.java +++ b/src/com/android/settings/bluetooth/BluetoothSettings.java @@ -16,11 +16,12 @@ package com.android.settings.bluetooth; -import android.app.Activity; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.preference.CheckBoxPreference; +import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; import android.util.Log; import android.view.View; @@ -31,13 +32,12 @@ import com.android.settings.R; * BluetoothSettings is the Settings screen for Bluetooth configuration and * connection management. */ -public class BluetoothSettings extends DeviceListPreferenceFragment - implements LocalBluetoothManager.Callback, View.OnClickListener { - +public final class BluetoothSettings extends DeviceListPreferenceFragment { private static final String TAG = "BluetoothSettings"; private static final String KEY_BT_CHECKBOX = "bt_checkbox"; private static final String KEY_BT_DISCOVERABLE = "bt_discoverable"; + private static final String KEY_BT_DISCOVERABLE_TIMEOUT = "bt_discoverable_timeout"; private static final String KEY_BT_NAME = "bt_name"; private static final String KEY_BT_SHOW_RECEIVED = "bt_show_received_files"; @@ -49,14 +49,22 @@ public class BluetoothSettings extends DeviceListPreferenceFragment private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES = "android.btopp.intent.action.OPEN_RECEIVED_FILES"; - void addPreferencesForActivity(Activity activity) { + /** Initialize the filter to show bonded devices only. */ + public BluetoothSettings() { + super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER); + } + + @Override + void addPreferencesForActivity() { addPreferencesFromResource(R.xml.bluetooth_settings); - mEnabler = new BluetoothEnabler(activity, + mEnabler = new BluetoothEnabler(getActivity(), mLocalAdapter, (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX)); - mDiscoverableEnabler = new BluetoothDiscoverableEnabler(activity, - (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE)); + mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(), + mLocalAdapter, + (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE), + (ListPreference) findPreference(KEY_BT_DISCOVERABLE_TIMEOUT)); mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME); } @@ -83,6 +91,27 @@ public class BluetoothSettings extends DeviceListPreferenceFragment mEnabler.pause(); } + private final View.OnClickListener mListener = new View.OnClickListener() { + public void onClick(View v) { + // User clicked on advanced options icon for a device in the list + if (v.getTag() instanceof CachedBluetoothDevice) { + CachedBluetoothDevice + device = (CachedBluetoothDevice) v.getTag(); + + Preference pref = new Preference(getActivity()); + pref.setTitle(device.getName()); + pref.setFragment(DeviceProfilesSettings.class.getName()); + pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, + device.getDevice()); + ((PreferenceActivity) getActivity()) + .onPreferenceStartFragment(BluetoothSettings.this, + pref); + } else { + Log.w(TAG, "onClick() called for other View: " + v); + } + } + }; + @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { @@ -100,9 +129,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment if (bondState == BluetoothDevice.BOND_BONDED) { // add to "Paired devices" list after remote-initiated pairing if (mDevicePreferenceMap.get(cachedDevice) == null) { - if (addDevicePreference(cachedDevice)) { - createDevicePreference(cachedDevice); - } + createDevicePreference(cachedDevice); } } else if (bondState == BluetoothDevice.BOND_NONE) { // remove unpaired device from paired devices list @@ -111,21 +138,11 @@ public class BluetoothSettings extends DeviceListPreferenceFragment } /** - * Additional check to only add paired devices to list. - */ - boolean addDevicePreference(CachedBluetoothDevice cachedDevice) { - if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) { - return super.addDevicePreference(cachedDevice); - } else { - return false; - } - } - - /** * Add a listener, which enables the advanced settings icon. * @param preference the newly added preference */ + @Override void initDevicePreference(BluetoothDevicePreference preference) { - preference.setOnSettingsClickListener(this); + preference.setOnSettingsClickListener(mListener); } } diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java index db20411bc..0bc816c9d 100644 --- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java +++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java @@ -16,30 +16,18 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - -import android.app.AlertDialog; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; +import android.bluetooth.BluetoothProfile; import android.os.ParcelUuid; import android.os.SystemClock; -import android.preference.Preference; -import android.preference.PreferenceActivity; import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.Map; /** * CachedBluetoothDevice represents a remote Bluetooth device. It contains @@ -47,27 +35,23 @@ import java.util.Map; * functionality that can be performed on the device (connect, pair, disconnect, * etc.). */ -class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { +final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { private static final String TAG = "CachedBluetoothDevice"; - private static final boolean D = LocalBluetoothManager.D; - private static final boolean V = LocalBluetoothManager.V; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Utils.V; + private final LocalBluetoothAdapter mLocalAdapter; + private final LocalBluetoothProfileManager mProfileManager; private final BluetoothDevice mDevice; private String mName; private short mRssi; private BluetoothClass mBtClass; - private Context mContext; - private List<Profile> mProfiles = new ArrayList<Profile>(); + private final List<LocalBluetoothProfile> mProfiles = + new ArrayList<LocalBluetoothProfile>(); private boolean mVisible; - private final LocalBluetoothManager mLocalManager; - - private AlertDialog mDialog = null; - - private List<Callback> mCallbacks = new ArrayList<Callback>(); + private final Collection<Callback> mCallbacks = new ArrayList<Callback>(); /** * When we connect to multiple profiles, we only want to display a single @@ -95,186 +79,62 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * @param profile Profile to describe * @return Description of the device and profile */ - private String describe(Profile profile) { + private String describe(LocalBluetoothProfile profile) { StringBuilder sb = new StringBuilder(); sb.append("Address:").append(mDevice); if (profile != null) { - sb.append(" Profile:").append(profile.name()); + sb.append(" Profile:").append(profile); } return sb.toString(); } - public void onProfileStateChanged(Profile profile, int newProfileState) { - if (D) { - Log.d(TAG, "onProfileStateChanged: profile " + profile.toString() + + void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { + if (Utils.D) { + Log.d(TAG, "onProfileStateChanged: profile " + profile + " newProfileState " + newProfileState); } - final LocalBluetoothProfileManager pm = - LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); - if (pm == null) return; - int newState = pm.convertState(newProfileState); - - if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED) { + if (newProfileState == BluetoothProfile.STATE_CONNECTED) { if (!mProfiles.contains(profile)) { mProfiles.add(profile); } } } - CachedBluetoothDevice(Context context, BluetoothDevice device) { - mLocalManager = LocalBluetoothManager.getInstance(context); - if (mLocalManager == null) { - throw new IllegalStateException( - "Cannot use CachedBluetoothDevice without Bluetooth hardware"); - } - + CachedBluetoothDevice(LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + mLocalAdapter = adapter; + mProfileManager = profileManager; mDevice = device; - mContext = context; - fillData(); } - public void onClicked() { - int bondState = getBondState(); - - if (isConnected()) { - askDisconnect(); - } else if (bondState == BluetoothDevice.BOND_BONDED) { - connect(true); - } else if (bondState == BluetoothDevice.BOND_NONE) { - pair(); - } - } - - public void disconnect() { - for (Profile profile : mProfiles) { + void disconnect() { + for (LocalBluetoothProfile profile : mProfiles) { disconnect(profile); } } - public void disconnect(Profile profile) { - LocalBluetoothProfileManager profileManager = - LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); - if (profileManager.disconnect(mDevice)) { - if (D) { + void disconnect(LocalBluetoothProfile profile) { + if (profile.disconnect(mDevice)) { + if (Utils.D) { Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); } } } - public void askDisconnect() { - Context context = mLocalManager.getForegroundActivity(); - if (context == null) { - // Cannot ask, since we need an activity context - disconnect(); + void connect(boolean connectAllProfiles) { + if (!ensurePaired()) { 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(); - } - }; - - showDisconnectDialog(context, disconnectListener, message); - } - - public void askDisconnect(final Profile profile) { - Context context = mLocalManager.getForegroundActivity(); - if (context == null) { - // Cannot ask, since we need an activity context - disconnect(profile); - return; - } - - Resources res = context.getResources(); - - String name = getName(); - if (TextUtils.isEmpty(name)) { - name = res.getString(R.string.bluetooth_device); - } - int disconnectMessage; - switch (profile) { - case A2DP: - disconnectMessage = R.string.bluetooth_disconnect_a2dp_profile; - break; - case HEADSET: - disconnectMessage = R.string.bluetooth_disconnect_headset_profile; - break; - case HID: - disconnectMessage = R.string.bluetooth_disconnect_hid_profile; - break; - case PAN: - disconnectMessage = R.string.bluetooth_disconnect_pan_profile; - break; - default: - Log.w(TAG, "askDisconnect: unexpected profile " + profile); - disconnectMessage = R.string.bluetooth_disconnect_blank; - break; - } - String message = res.getString(disconnectMessage, name); - - DialogInterface.OnClickListener disconnectListener = - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - disconnect(profile); - } - }; - - showDisconnectDialog(context, disconnectListener, message); - } - - private void showDisconnectDialog(Context context, - DialogInterface.OnClickListener disconnectListener, - String message) { - if (mDialog == null) { - mDialog = new AlertDialog.Builder(context) - .setPositiveButton(android.R.string.ok, disconnectListener) - .setNegativeButton(android.R.string.cancel, null) - .create(); - } else { - if (mDialog.isShowing()) { - mDialog.dismiss(); - } - // use disconnectListener for the correct profile(s) - CharSequence okText = context.getText(android.R.string.ok); - mDialog.setButton(DialogInterface.BUTTON_POSITIVE, - okText, disconnectListener); - } - mDialog.setTitle(getName()); - mDialog.setMessage(message); - mDialog.show(); - } - - @Override - protected void finalize() throws Throwable { - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - - super.finalize(); - } - - public void connect(boolean connectAllProfiles) { - if (!ensurePaired()) return; - mConnectAttempted = SystemClock.elapsedRealtime(); - connectWithoutResettingTimer(connectAllProfiles); } - /*package*/ void onBondingDockConnect() { + void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. connect(false); @@ -282,7 +142,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { private void connectWithoutResettingTimer(boolean connectAllProfiles) { // Try to initialize the profiles if they were not. - if (mProfiles.size() == 0) { + if (mProfiles.isEmpty()) { if (!updateProfiles()) { // If UUIDs are not available yet, connect will be happen // upon arrival of the ACTION_UUID intent. @@ -295,98 +155,85 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { mIsConnectingErrorPossible = true; int preferredProfiles = 0; - for (Profile profile : mProfiles) { + for (LocalBluetoothProfile profile : mProfiles) { if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - if (profileManager.isPreferred(mDevice)) { + if (profile.isPreferred(mDevice)) { ++preferredProfiles; - connectInt(this, profile); + connectInt(profile); } } } if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles); if (preferredProfiles == 0) { - connectAllAutoConnectableProfiles(); + connectAutoConnectableProfiles(); } } - private void connectAllAutoConnectableProfiles() { - if (!ensurePaired()) return; - + private void connectAutoConnectableProfiles() { + if (!ensurePaired()) { + return; + } // Reset the only-show-one-error-dialog tracking variable mIsConnectingErrorPossible = true; - for (Profile profile : mProfiles) { + for (LocalBluetoothProfile profile : mProfiles) { if (profile.isAutoConnectable()) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - profileManager.setPreferred(mDevice, true); - connectInt(this, profile); + profile.setPreferred(mDevice, true); + connectInt(profile); } } } - public void connect(Profile profile) { + /** + * Connect this device to the specified profile. + * + * @param profile the profile to use with the remote device + */ + void connectProfile(LocalBluetoothProfile profile) { mConnectAttempted = SystemClock.elapsedRealtime(); // Reset the only-show-one-error-dialog tracking variable mIsConnectingErrorPossible = true; - connectInt(this, profile); + connectInt(profile); } - private boolean connectInt(CachedBluetoothDevice cachedDevice, Profile profile) { - if (!cachedDevice.ensurePaired()) return false; - - LocalBluetoothProfileManager profileManager = - LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); - - if (profileManager.connect(cachedDevice.mDevice)) { - if (D) { + private void connectInt(LocalBluetoothProfile profile) { + if (!ensurePaired()) { + return; + } + if (profile.connect(mDevice)) { + if (Utils.D) { Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); } - return true; + return; } - Log.i(TAG, "Failed to connect " + profile.toString() + " to " + cachedDevice.mName); - - return false; - } - - public void showConnectingError() { - if (!mIsConnectingErrorPossible) return; - mIsConnectingErrorPossible = false; - - mLocalManager.showError(mDevice, - R.string.bluetooth_connecting_error_message); + Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName); } private boolean ensurePaired() { if (getBondState() == BluetoothDevice.BOND_NONE) { - pair(); + startPairing(); return false; } else { return true; } } - public void pair() { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - + boolean startPairing() { // Pairing is unreliable while scanning, so cancel discovery - if (adapter.isDiscovering()) { - adapter.cancelDiscovery(); + if (mLocalAdapter.isDiscovering()) { + mLocalAdapter.cancelDiscovery(); } if (!mDevice.createBond()) { - mLocalManager.showError(mDevice, - R.string.bluetooth_pairing_error_message); - return; + return false; } mConnectAfterPairing = true; // auto-connect after pairing + return true; } - public void unpair() { + void unpair() { disconnect(); int state = getBondState(); @@ -396,14 +243,14 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } if (state != BluetoothDevice.BOND_NONE) { - final BluetoothDevice dev = getDevice(); + final BluetoothDevice dev = mDevice; if (dev != null) { final boolean successful = dev.removeBond(); if (successful) { - if (D) { + if (Utils.D) { Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); } - } else if (V) { + } else if (Utils.V) { Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + describe(null)); } @@ -411,6 +258,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } + // TODO: do any of these need to run async on a background thread? private void fillData() { fetchName(); fetchBtClass(); @@ -421,15 +269,15 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { dispatchAttributesChanged(); } - public BluetoothDevice getDevice() { + BluetoothDevice getDevice() { return mDevice; } - public String getName() { + String getName() { return mName; } - public void setName(String name) { + void setName(String name) { if (!mName.equals(name)) { if (TextUtils.isEmpty(name)) { // TODO: use friendly name for unknown device (bug 1181856) @@ -442,7 +290,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } - public void refreshName() { + void refreshName() { fetchName(); dispatchAttributesChanged(); } @@ -452,15 +300,15 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { if (TextUtils.isEmpty(mName)) { mName = mDevice.getAddress(); - if (DEBUG) Log.d(TAG, "Default to address. Device has no name (yet) " + mName); + if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName); } } - public void refresh() { + void refresh() { dispatchAttributesChanged(); } - public boolean isVisible() { + boolean isVisible() { return mVisible; } @@ -471,7 +319,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } - public int getBondState() { + int getBondState() { return mDevice.getBondState(); } @@ -487,11 +335,10 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * * @return Whether it is connected. */ - public boolean isConnected() { - for (Profile profile : mProfiles) { - int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) - .getConnectionStatus(mDevice); - if (SettingsBtStatus.isConnectionStatusConnected(status)) { + boolean isConnected() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = profile.getConnectionStatus(mDevice); + if (status == BluetoothProfile.STATE_CONNECTED) { return true; } } @@ -499,78 +346,21 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { return false; } - public boolean isConnectedProfile(Profile profile) { - int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) - .getConnectionStatus(mDevice); - if (SettingsBtStatus.isConnectionStatusConnected(status)) { - return true; - } + boolean isConnectedProfile(LocalBluetoothProfile profile) { + int status = profile.getConnectionStatus(mDevice); + return status == BluetoothProfile.STATE_CONNECTED; - return false; } - public boolean isBusy() { - for (Profile profile : mProfiles) { - int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) - .getConnectionStatus(mDevice); - if (SettingsBtStatus.isConnectionStatusBusy(status)) { + boolean isBusy() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = profile.getConnectionStatus(mDevice); + if (status == BluetoothProfile.STATE_CONNECTING + || status == BluetoothProfile.STATE_DISCONNECTING) { return true; } } - - if (getBondState() == BluetoothDevice.BOND_BONDING) { - return true; - } - - return false; - } - - public int getBtClassDrawable() { - if (mBtClass != null) { - switch (mBtClass.getMajorDeviceClass()) { - case BluetoothClass.Device.Major.COMPUTER: - return R.drawable.ic_bt_laptop; - - case BluetoothClass.Device.Major.PHONE: - return R.drawable.ic_bt_cellphone; - - case BluetoothClass.Device.Major.PERIPHERAL: - return getHidClassDrawable(); - } - } else { - Log.w(TAG, "mBtClass is null"); - } - - if (mProfiles.size() > 0) { - 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; - } - } else if (mBtClass != null) { - if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { - return R.drawable.ic_bt_headphones_a2dp; - - } - if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { - return R.drawable.ic_bt_headset_hfp; - } - } - return 0; - } - - private int getHidClassDrawable() { - switch (mBtClass.getDeviceClass()) { - case BluetoothClass.Device.PERIPHERAL_KEYBOARD: - case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: - return R.drawable.ic_bt_keyboard_hid; - - case BluetoothClass.Device.PERIPHERAL_POINTING: - return R.drawable.ic_bt_pointing_hid; - - default: - return R.drawable.ic_bt_misc_hid; - } + return getBondState() == BluetoothDevice.BOND_BONDING; } /** @@ -584,11 +374,10 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { ParcelUuid[] uuids = mDevice.getUuids(); if (uuids == null) return false; - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - ParcelUuid[] localUuids = adapter.getUuids(); + ParcelUuid[] localUuids = mLocalAdapter.getUuids(); if (localUuids == null) return false; - LocalBluetoothProfileManager.updateProfiles(uuids, localUuids, mProfiles); + mProfileManager.updateProfiles(uuids, localUuids, mProfiles); if (DEBUG) { Log.e(TAG, "updating profiles for " + mDevice.getName()); @@ -596,8 +385,8 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); Log.v(TAG, "UUID:"); - for (int i = 0; i < uuids.length; i++) { - Log.v(TAG, " " + uuids[i]); + for (ParcelUuid uuid : uuids) { + Log.v(TAG, " " + uuid); } } return true; @@ -607,7 +396,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * Refreshes the UI for the BT class, including fetching the latest value * for the class. */ - public void refreshBtClass() { + void refreshBtClass() { fetchBtClass(); dispatchAttributesChanged(); } @@ -615,7 +404,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { /** * Refreshes the UI when framework alerts us of a UUID change. */ - public void onUuidChanged() { + void onUuidChanged() { updateProfiles(); if (DEBUG) { @@ -627,7 +416,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * If a connect was attempted earlier without any UUID, we will do the * connect now. */ - if (mProfiles.size() > 0 + if (!mProfiles.isEmpty() && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock .elapsedRealtime()) { connectWithoutResettingTimer(false); @@ -635,7 +424,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { dispatchAttributesChanged(); } - public void onBondingStateChanged(int bondState) { + void onBondingStateChanged(int bondState) { if (bondState == BluetoothDevice.BOND_NONE) { mProfiles.clear(); mConnectAfterPairing = false; // cancel auto-connect @@ -653,52 +442,25 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } - public void setBtClass(BluetoothClass btClass) { + void setBtClass(BluetoothClass btClass) { if (btClass != null && mBtClass != btClass) { mBtClass = btClass; dispatchAttributesChanged(); } } - public int getSummary() { - for (Profile profile : mProfiles) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - int connectionStatus = profileManager.getConnectionStatus(mDevice); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) || - connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING || - connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); - } - } - - return SettingsBtStatus.getPairingStatusSummary(getBondState()); + BluetoothClass getBtClass() { + return mBtClass; } - public Map<Profile, Drawable> getProfileIcons() { - Map<Profile, Drawable> drawables = new HashMap<Profile, Drawable>(); - - for (Profile profile : mProfiles) { - int iconResource; - if (profile == Profile.HID && mBtClass != null) { - iconResource = getHidClassDrawable(); - } else { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - iconResource = profileManager.getDrawableResource(); - } - if (iconResource != 0) { - drawables.put(profile, mContext.getResources().getDrawable(iconResource)); - } - } - - return drawables; + List<LocalBluetoothProfile> getProfiles() { + return Collections.unmodifiableList(mProfiles); } - public List<Profile> getConnectableProfiles() { - ArrayList<Profile> connectableProfiles = new ArrayList<Profile>(); - for (Profile profile : mProfiles) { + List<LocalBluetoothProfile> getConnectableProfiles() { + List<LocalBluetoothProfile> connectableProfiles = + new ArrayList<LocalBluetoothProfile>(); + for (LocalBluetoothProfile profile : mProfiles) { if (profile.isConnectable()) { connectableProfiles.add(profile); } @@ -706,36 +468,13 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { return connectableProfiles; } - public void onClickedAdvancedOptions(SettingsPreferenceFragment fragment) { - // TODO: Verify if there really is a case when there's no foreground - // activity - - // 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_DEVICE, - // mDevice); - // context.startActivity(intent); - Preference pref = new Preference(fragment.getActivity()); - pref.setTitle(getName()); - pref.setFragment(DeviceProfilesSettings.class.getName()); - pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, mDevice); - ((PreferenceActivity) fragment.getActivity()).onPreferenceStartFragment(fragment, pref); - } - - public void registerCallback(Callback callback) { + void registerCallback(Callback callback) { synchronized (mCallbacks) { mCallbacks.add(callback); } } - public void unregisterCallback(Callback callback) { + void unregisterCallback(Callback callback) { synchronized (mCallbacks) { mCallbacks.remove(callback); } @@ -757,9 +496,8 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { @Override public boolean equals(Object o) { if ((o == null) || !(o instanceof CachedBluetoothDevice)) { - throw new ClassCastException(); + return false; } - return mDevice.equals(((CachedBluetoothDevice) o).mDevice); } @@ -768,11 +506,12 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { return mDevice.getAddress().hashCode(); } + // This comparison uses non-final fields so the sort order may change + // when device attributes change (such as bonding state). Settings + // will completely refresh the device list when this happens. public int compareTo(CachedBluetoothDevice another) { - int comparison; - // Connected above not connected - comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); + int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); if (comparison != 0) return comparison; // Paired above not paired @@ -789,7 +528,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { if (comparison != 0) return comparison; // Fallback on name - return getName().compareTo(another.getName()); + return mName.compareTo(another.mName); } public interface Callback { diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java index 3ee8bc26d..ab71ece7e 100644 --- a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java +++ b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java @@ -16,122 +16,83 @@ package com.android.settings.bluetooth; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.util.Log; - -import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothManager.Callback; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Set; /** * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. */ -class CachedBluetoothDeviceManager { - private static final String TAG = "CachedBluetoothDeviceManager"; - - final LocalBluetoothManager mLocalManager; - final List<Callback> mCallbacks; - - final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>(); - - public CachedBluetoothDeviceManager(LocalBluetoothManager localManager) { - mLocalManager = localManager; - mCallbacks = localManager.getCallbacks(); - } +final class CachedBluetoothDeviceManager { +// private static final String TAG = "CachedBluetoothDeviceManager"; - private synchronized boolean readPairedDevices() { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - Set<BluetoothDevice> bondedDevices = adapter.getBondedDevices(); - if (bondedDevices == null) return false; - - boolean deviceAdded = false; - for (BluetoothDevice device : bondedDevices) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) { - cachedDevice = new CachedBluetoothDevice(mLocalManager.getContext(), device); - mCachedDevices.add(cachedDevice); - dispatchDeviceAdded(cachedDevice); - deviceAdded = true; - } - } - - return deviceAdded; - } + private final List<CachedBluetoothDevice> mCachedDevices = + new ArrayList<CachedBluetoothDevice>(); - public synchronized List<CachedBluetoothDevice> getCachedDevicesCopy() { + public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() { return new ArrayList<CachedBluetoothDevice>(mCachedDevices); } - void onBluetoothStateChanged(boolean enabled) { - if (enabled) { - readPairedDevices(); - } - } - - public synchronized void onDeviceAppeared(BluetoothDevice device, short rssi, - BluetoothClass btClass, String name) { - boolean deviceAdded = false; - - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) { - cachedDevice = new CachedBluetoothDevice(mLocalManager.getContext(), device); - mCachedDevices.add(cachedDevice); - deviceAdded = true; - } - cachedDevice.setRssi(rssi); - cachedDevice.setBtClass(btClass); - cachedDevice.setName(name); - cachedDevice.setVisible(true); - - if (deviceAdded) { - dispatchDeviceAdded(cachedDevice); - } - } - - public synchronized void onDeviceDisappeared(BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) return; - + public boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) { cachedDevice.setVisible(false); - checkForDeviceRemoval(cachedDevice); + return checkForDeviceRemoval(cachedDevice); } - private void checkForDeviceRemoval(CachedBluetoothDevice cachedDevice) { + private boolean checkForDeviceRemoval( + CachedBluetoothDevice cachedDevice) { if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE && !cachedDevice.isVisible()) { // If device isn't paired, remove it altogether mCachedDevices.remove(cachedDevice); - dispatchDeviceDeleted(cachedDevice); + return true; // dispatch device deleted } + return false; } - public synchronized void onDeviceNameUpdated(BluetoothDevice device) { + public void onDeviceNameUpdated(BluetoothDevice device) { CachedBluetoothDevice cachedDevice = findDevice(device); if (cachedDevice != null) { cachedDevice.refreshName(); } } - public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) { - - for (int i = mCachedDevices.size() - 1; i >= 0; i--) { - CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); - + /** + * Search for existing {@link CachedBluetoothDevice} or return null + * if this device isn't in the cache. Use {@link #addDevice} + * to create and return a new {@link CachedBluetoothDevice} for + * a newly discovered {@link BluetoothDevice}. + * + * @param device the address of the Bluetooth device + * @return the cached device object for this device, or null if it has + * not been previously seen + */ + CachedBluetoothDevice findDevice(BluetoothDevice device) { + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (cachedDevice.getDevice().equals(device)) { return cachedDevice; } } - return null; } /** + * Create and return a new {@link CachedBluetoothDevice}. This assumes + * that {@link #findDevice} has already been called and returned null. + * @param device the address of the new Bluetooth device + * @return the newly created CachedBluetoothDevice object + */ + CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + CachedBluetoothDevice newDevice = new CachedBluetoothDevice(adapter, profileManager, + device); + mCachedDevices.add(newDevice); + return newDevice; + } + + /** * Attempts to get the name of a remote device, otherwise returns the address. * * @param device The remote device. @@ -139,122 +100,23 @@ class CachedBluetoothDeviceManager { */ public String getName(BluetoothDevice device) { CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice != null) return cachedDevice.getName(); - - String name = device.getName(); - if (name != null) return name; - - return device.getAddress(); - } - - private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onDeviceAdded(cachedDevice); - } - } - - // TODO: divider between prev paired/connected and scanned - } - - private void dispatchDeviceDeleted(CachedBluetoothDevice cachedDevice) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onDeviceDeleted(cachedDevice); - } - } - } - - private void dispatchDeviceBondStateChanged( - CachedBluetoothDevice cachedDevice, int bondState) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onDeviceBondStateChanged(cachedDevice, bondState); - } - } - } - - public synchronized void onBondingStateChanged(BluetoothDevice device, int bondState) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) { - if (!readPairedDevices()) { - Log.e(TAG, "Got bonding state changed for " + device + - ", but we have no record of that device."); - return; - } - cachedDevice = findDevice(device); - if (cachedDevice == null) { - Log.e(TAG, "Got bonding state changed for " + device + - ", but device not added in cache."); - return; - } + if (cachedDevice != null) { + return cachedDevice.getName(); } - dispatchDeviceBondStateChanged(cachedDevice, bondState); - cachedDevice.onBondingStateChanged(bondState); - } - - /** - * Called when we have reached the un-bond state. - * - * @param device The remote device. - * @param reason The reason, one of the error reasons from - * BluetoothDevice.UNBOND_REASON_* - */ - public synchronized void showUnbondMessage(BluetoothDevice device, int reason) { - int errorMsg; - - switch(reason) { - case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: - errorMsg = R.string.bluetooth_pairing_pin_error_message; - mLocalManager.showError(device, errorMsg); - break; - case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: - errorMsg = R.string.bluetooth_pairing_rejected_error_message; - mLocalManager.showError(device, errorMsg); - break; - case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: - errorMsg = R.string.bluetooth_pairing_device_down_error_message; - mLocalManager.showError(device, errorMsg); - break; - case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: - case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: - case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: - case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: - errorMsg = R.string.bluetooth_pairing_error_message; - mLocalManager.showError(device, errorMsg); - break; - default: - Log.w(TAG, "showUnbondMessage: Not displaying any message for reason:" + reason); - break; + String name = device.getName(); + if (name != null) { + return name; } - } - public synchronized void onProfileStateChanged(BluetoothDevice device, Profile profile, - int newProfileState) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) return; - - cachedDevice.onProfileStateChanged(profile, newProfileState); - cachedDevice.refresh(); - } - - public synchronized void onConnectingError(BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == 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. - */ - cachedDevice.showConnectingError(); + return device.getAddress(); } public synchronized void onScanningStateChanged(boolean started) { if (!started) return; // If starting a new scan, clear old visibility + // Iterate in reverse order since devices may be removed. for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); cachedDevice.setVisible(false); diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java index 46fff6e01..a978e2392 100644 --- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java +++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java @@ -16,28 +16,18 @@ package com.android.settings.bluetooth; -import android.app.Activity; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothDevicePicker; -import android.bluetooth.BluetoothUuid; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; -import android.os.ParcelUuid; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; import android.util.Log; -import android.view.View; import com.android.settings.ProgressCategory; import com.android.settings.SettingsPreferenceFragment; -import java.util.List; +import java.util.Collection; import java.util.WeakHashMap; /** @@ -48,105 +38,96 @@ import java.util.WeakHashMap; * @see DevicePickerFragment * @see BluetoothFindNearby */ -public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFragment - implements LocalBluetoothManager.Callback, View.OnClickListener { +public abstract class DeviceListPreferenceFragment extends + SettingsPreferenceFragment implements BluetoothCallback { private static final String TAG = "DeviceListPreferenceFragment"; - static final String KEY_BT_DEVICE_LIST = "bt_device_list"; - static final String KEY_BT_SCAN = "bt_scan"; + private static final String KEY_BT_DEVICE_LIST = "bt_device_list"; + private static final String KEY_BT_SCAN = "bt_scan"; - int mFilterType; + private BluetoothDeviceFilter.Filter mFilter; - BluetoothDevice mSelectedDevice = null; + BluetoothDevice mSelectedDevice; + LocalBluetoothAdapter mLocalAdapter; LocalBluetoothManager mLocalManager; private PreferenceCategory mDeviceList; - WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = + final WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>(); - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - onBluetoothStateChanged(mLocalManager.getBluetoothState()); - } - } - }; + DeviceListPreferenceFragment() { + mFilter = BluetoothDeviceFilter.ALL_FILTER; + } + + DeviceListPreferenceFragment(BluetoothDeviceFilter.Filter filter) { + mFilter = filter; + } + + final void setFilter(int filterType) { + mFilter = BluetoothDeviceFilter.getFilter(filterType); + } @Override - public void onActivityCreated(Bundle savedInstanceState) { - // We delay calling super.onActivityCreated(). See WifiSettings.java for more info. + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - final Activity activity = getActivity(); - mLocalManager = LocalBluetoothManager.getInstance(activity); + mLocalManager = LocalBluetoothManager.getInstance(getActivity()); if (mLocalManager == null) { - finish(); + Log.e(TAG, "Bluetooth is not supported on this device"); + return; } + mLocalAdapter = mLocalManager.getBluetoothAdapter(); - mFilterType = BluetoothDevicePicker.FILTER_TYPE_ALL; - - if (getPreferenceScreen() != null) getPreferenceScreen().removeAll(); - - addPreferencesForActivity(activity); + addPreferencesForActivity(); mDeviceList = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST); - - super.onActivityCreated(savedInstanceState); + if (mDeviceList == null) { + Log.e(TAG, "Could not find device list preference object!"); + } } /** Add preferences from the subclass. */ - abstract void addPreferencesForActivity(Activity activity); + abstract void addPreferencesForActivity(); @Override public void onResume() { super.onResume(); - mLocalManager.registerCallback(this); - - updateProgressUi(mLocalManager.getBluetoothAdapter().isDiscovering()); - - IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - getActivity().registerReceiver(mReceiver, intentFilter); mLocalManager.setForegroundActivity(getActivity()); + mLocalManager.getEventManager().registerCallback(this); + + updateProgressUi(mLocalAdapter.isDiscovering()); } @Override public void onPause() { super.onPause(); - mLocalManager.stopScanning(); + + mLocalAdapter.stopScanning(); mLocalManager.setForegroundActivity(null); + mLocalManager.getEventManager().unregisterCallback(this); + mDevicePreferenceMap.clear(); mDeviceList.removeAll(); - getActivity().unregisterReceiver(mReceiver); - - mLocalManager.unregisterCallback(this); } void addDevices() { - List<CachedBluetoothDevice> cachedDevices = + Collection<CachedBluetoothDevice> cachedDevices = mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); for (CachedBluetoothDevice cachedDevice : cachedDevices) { onDeviceAdded(cachedDevice); } } - public void onClick(View v) { - // User clicked on advanced options icon for a device in the list - if (v.getTag() instanceof CachedBluetoothDevice) { - CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag(); - device.onClickedAdvancedOptions(this); - } - } - @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (KEY_BT_SCAN.equals(preference.getKey())) { - mLocalManager.startScanning(true); + mLocalAdapter.startScanning(true); return true; } @@ -162,7 +143,7 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra } void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - btPreference.getCachedDevice().onClicked(); + btPreference.onClicked(); } public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { @@ -171,71 +152,11 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra return; } - if (addDevicePreference(cachedDevice)) { + if (mFilter.matches(cachedDevice.getDevice())) { createDevicePreference(cachedDevice); } } - /** - * Determine whether to add the new device to the list. - * @param cachedDevice the newly discovered device - * @return true if the device should be added; false otherwise - */ - boolean addDevicePreference(CachedBluetoothDevice cachedDevice) { - ParcelUuid[] uuids = cachedDevice.getDevice().getUuids(); - BluetoothClass bluetoothClass = cachedDevice.getDevice().getBluetoothClass(); - - switch(mFilterType) { - case BluetoothDevicePicker.FILTER_TYPE_TRANSFER: - if (uuids != null) { - if (BluetoothUuid.containsAnyUuid(uuids, - LocalBluetoothProfileManager.OPP_PROFILE_UUIDS)) return true; - } - if (bluetoothClass != null - && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_OPP)) { - return true; - } - break; - case BluetoothDevicePicker.FILTER_TYPE_AUDIO: - if (uuids != null) { - if (BluetoothUuid.containsAnyUuid(uuids, - LocalBluetoothProfileManager.A2DP_SINK_PROFILE_UUIDS)) return true; - - if (BluetoothUuid.containsAnyUuid(uuids, - LocalBluetoothProfileManager.HEADSET_PROFILE_UUIDS)) return true; - } else if (bluetoothClass != null) { - if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) return true; - - if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) return true; - } - break; - case BluetoothDevicePicker.FILTER_TYPE_PANU: - if (uuids != null) { - if (BluetoothUuid.containsAnyUuid(uuids, - LocalBluetoothProfileManager.PANU_PROFILE_UUIDS)) return true; - - } - if (bluetoothClass != null - && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_PANU)) { - return true; - } - break; - case BluetoothDevicePicker.FILTER_TYPE_NAP: - if (uuids != null) { - if (BluetoothUuid.containsAnyUuid(uuids, - LocalBluetoothProfileManager.NAP_PROFILE_UUIDS)) return true; - } - if (bluetoothClass != null - && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_NAP)) { - return true; - } - break; - default: - return true; - } - return false; - } - void createDevicePreference(CachedBluetoothDevice cachedDevice) { BluetoothDevicePreference preference = new BluetoothDevicePreference( getActivity(), cachedDevice); @@ -252,7 +173,8 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra void initDevicePreference(BluetoothDevicePreference preference) { } public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { - BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice); + BluetoothDevicePreference preference = mDevicePreferenceMap.remove( + cachedDevice); if (preference != null) { mDeviceList.removePreference(preference); } @@ -268,15 +190,9 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra } } - void onBluetoothStateChanged(int bluetoothState) { + public void onBluetoothStateChanged(int bluetoothState) { if (bluetoothState == BluetoothAdapter.STATE_OFF) { updateProgressUi(false); } } - - void sendDevicePickedIntent(BluetoothDevice device) { - Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - getActivity().sendBroadcast(intent); - } } diff --git a/src/com/android/settings/bluetooth/DevicePickerActivity.java b/src/com/android/settings/bluetooth/DevicePickerActivity.java index c29fafb64..8f6e0dfe3 100644 --- a/src/com/android/settings/bluetooth/DevicePickerActivity.java +++ b/src/com/android/settings/bluetooth/DevicePickerActivity.java @@ -25,7 +25,7 @@ import android.os.Bundle; * Activity for Bluetooth device picker dialog. The device picker logic * is implemented in the {@link BluetoothSettings} fragment. */ -public class DevicePickerActivity extends Activity { +public final class DevicePickerActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index d3e3d6925..126df022c 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -16,12 +16,11 @@ package com.android.settings.bluetooth; -import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevicePicker; import android.content.Intent; -import android.util.Log; +import android.os.Bundle; import com.android.settings.R; @@ -29,36 +28,42 @@ import com.android.settings.R; * BluetoothSettings is the Settings screen for Bluetooth configuration and * connection management. */ -public class DevicePickerFragment extends DeviceListPreferenceFragment { - - private static final String TAG = "BluetoothDevicePicker"; +public final class DevicePickerFragment extends DeviceListPreferenceFragment { private boolean mNeedAuth; private String mLaunchPackage; private String mLaunchClass; - void addPreferencesForActivity(Activity activity) { - Intent intent = activity.getIntent(); + @Override + void addPreferencesForActivity() { + addPreferencesFromResource(R.xml.device_picker); + + Intent intent = getActivity().getIntent(); mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false); - mFilterType = intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, - BluetoothDevicePicker.FILTER_TYPE_ALL); + setFilter(intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, + BluetoothDevicePicker.FILTER_TYPE_ALL)); mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE); mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS); + } - activity.setTitle(activity.getString(R.string.device_picker)); - addPreferencesFromResource(R.xml.device_picker); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().setTitle(getString(R.string.device_picker)); } @Override public void onResume() { super.onResume(); addDevices(); - mLocalManager.startScanning(true); + mLocalAdapter.startScanning(true); } + @Override void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - mLocalManager.stopScanning(); - mLocalManager.persistSelectedDeviceInPicker(mSelectedDevice.getAddress()); + mLocalAdapter.stopScanning(); + LocalBluetoothPreferences.persistSelectedDeviceInPicker( + getActivity(), mSelectedDevice.getAddress()); if ((btPreference.getCachedDevice().getBondState() == BluetoothDevice.BOND_BONDED) || !mNeedAuth) { sendDevicePickedIntent(mSelectedDevice); @@ -79,15 +84,16 @@ public class DevicePickerFragment extends DeviceListPreferenceFragment { } } - void onBluetoothStateChanged(int bluetoothState) { + @Override + public void onBluetoothStateChanged(int bluetoothState) { super.onBluetoothStateChanged(bluetoothState); if (bluetoothState == BluetoothAdapter.STATE_ON) { - mLocalManager.startScanning(false); + mLocalAdapter.startScanning(false); } } - void sendDevicePickedIntent(BluetoothDevice device) { + private void sendDevicePickedIntent(BluetoothDevice device) { Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); if (mLaunchPackage != null && mLaunchClass != null) { diff --git a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java index f39eabdc4..307125cb5 100644 --- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java +++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java @@ -16,15 +16,12 @@ package com.android.settings.bluetooth; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - -import android.bluetooth.BluetoothClass; +import android.app.AlertDialog; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.DialogInterface; import android.os.Bundle; -import android.os.ParcelUuid; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.Preference; @@ -34,6 +31,9 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + import java.util.HashMap; /** @@ -41,7 +41,7 @@ import java.util.HashMap; * for a particular device, and allows them to be individually connected * (or disconnected). */ -public class DeviceProfilesSettings extends SettingsPreferenceFragment +public final class DeviceProfilesSettings extends SettingsPreferenceFragment implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener, View.OnClickListener { private static final String TAG = "DeviceProfilesSettings"; @@ -56,11 +56,15 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment private LocalBluetoothManager mManager; private CachedBluetoothDevice mCachedDevice; + private LocalBluetoothProfileManager mProfileManager; private PreferenceGroup mProfileContainer; private EditTextPreference mDeviceNamePref; - private final HashMap<String,CheckBoxPreference> mAutoConnectPrefs - = new HashMap<String,CheckBoxPreference>(); + + private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs + = new HashMap<LocalBluetoothProfile, CheckBoxPreference>(); + + private AlertDialog mDisconnectDialog; @Override public void onCreate(Bundle savedInstanceState) { @@ -74,51 +78,54 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment device = args.getParcelable(EXTRA_DEVICE); } + addPreferencesFromResource(R.xml.bluetooth_device_advanced); + getPreferenceScreen().setOrderingAsAdded(false); + mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER); + mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE); + if (device == null) { Log.w(TAG, "Activity started without a remote Bluetooth device"); finish(); - return; + return; // TODO: test this failure path } mManager = LocalBluetoothManager.getInstance(getActivity()); - mCachedDevice = mManager.getCachedDeviceManager().findDevice(device); + CachedBluetoothDeviceManager deviceManager = + mManager.getCachedDeviceManager(); + mProfileManager = mManager.getProfileManager(); + mCachedDevice = deviceManager.findDevice(device); if (mCachedDevice == null) { Log.w(TAG, "Device not found, cannot connect to it"); finish(); - return; + return; // TODO: test this failure path } - addPreferencesFromResource(R.xml.bluetooth_device_advanced); - getPreferenceScreen().setOrderingAsAdded(false); - - mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER); - - mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE); - mDeviceNamePref.setSummary(mCachedDevice.getName()); - mDeviceNamePref.setText(mCachedDevice.getName()); + String deviceName = mCachedDevice.getName(); + mDeviceNamePref.setSummary(deviceName); + mDeviceNamePref.setText(deviceName); mDeviceNamePref.setOnPreferenceChangeListener(this); // Set the title of the screen - findPreference(KEY_TITLE).setTitle(getResources() - .getString(R.string.bluetooth_device_advanced_title, mCachedDevice.getName())); + findPreference(KEY_TITLE).setTitle( + getString(R.string.bluetooth_device_advanced_title, + deviceName)); // Add a preference for each profile addPreferencesForProfiles(); } - private boolean isObjectPushSupported(BluetoothDevice device) { - ParcelUuid[] uuids = device.getUuids(); - BluetoothClass bluetoothClass = device.getBluetoothClass(); - return (uuids != null && BluetoothUuid.containsAnyUuid(uuids, - LocalBluetoothProfileManager.OPP_PROFILE_UUIDS)) || - (bluetoothClass != null && bluetoothClass.doesClassMatch( - BluetoothClass.PROFILE_OPP)); + @Override + public void onDestroy() { + super.onDestroy(); + if (mDisconnectDialog != null) { + mDisconnectDialog.dismiss(); + mDisconnectDialog = null; + } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(EXTRA_DEVICE, mCachedDevice.getDevice()); } @@ -141,7 +148,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } private void addPreferencesForProfiles() { - for (Profile profile : mCachedDevice.getConnectableProfiles()) { + for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) { Preference pref = createProfilePreference(profile); mProfileContainer.addPreference(pref); } @@ -155,21 +162,18 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment * @return A preference that allows the user to choose whether this profile * will be connected to. */ - private Preference createProfilePreference(Profile profile) { + private Preference createProfilePreference(LocalBluetoothProfile profile) { BluetoothProfilePreference pref = new BluetoothProfilePreference(getActivity(), profile); pref.setKey(profile.toString()); - pref.setTitle(profile.localizedString); + pref.setTitle(profile.getNameResource()); pref.setExpanded(false); pref.setPersistent(false); - pref.setOrder(getProfilePreferenceIndex(profile)); + pref.setOrder(getProfilePreferenceIndex(profile.getOrdinal())); pref.setOnExpandClickListener(this); - LocalBluetoothProfileManager profileManager = - LocalBluetoothProfileManager.getProfileManager(mManager, profile); - int iconResource = profileManager.getDrawableResource(); + int iconResource = profile.getDrawableResource(null); // FIXME: get BT class for this? if (iconResource != 0) { - pref.setProfileDrawable(mManager.getContext() - .getResources().getDrawable(iconResource)); + pref.setProfileDrawable(getResources().getDrawable(iconResource)); } /** @@ -186,7 +190,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { String key = preference.getKey(); if (preference instanceof BluetoothProfilePreference) { - onProfileClicked(Profile.valueOf(key)); + onProfileClicked(mProfileManager.getProfileByName(key)); return true; } else if (key.equals(KEY_UNPAIR)) { unpairDevice(); @@ -194,7 +198,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment return true; } - return false; + return super.onPreferenceTreeClick(screen, preference); } public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -202,10 +206,8 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment mCachedDevice.setName((String) newValue); } else if (preference instanceof CheckBoxPreference) { boolean autoConnect = (Boolean) newValue; - Profile prof = getProfileOf(preference); - LocalBluetoothProfileManager - .getProfileManager(mManager, prof) - .setPreferred(mCachedDevice.getDevice(), + LocalBluetoothProfile prof = getProfileOf(preference); + prof.setPreferred(mCachedDevice.getDevice(), autoConnect); return true; } else { @@ -215,20 +217,44 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment return true; } - private void onProfileClicked(Profile profile) { + private void onProfileClicked(LocalBluetoothProfile profile) { BluetoothDevice device = mCachedDevice.getDevice(); - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mManager, profile); - int status = profileManager.getConnectionStatus(device); + int status = profile.getConnectionStatus(device); boolean isConnected = - SettingsBtStatus.isConnectionStatusConnected(status); + status == BluetoothProfile.STATE_CONNECTED; if (isConnected) { - mCachedDevice.askDisconnect(profile); + askDisconnect(getActivity(), profile); } else { - mCachedDevice.connect(profile); + mCachedDevice.connectProfile(profile); + } + } + + private void askDisconnect(Context context, + final LocalBluetoothProfile profile) { + // local reference for callback + final CachedBluetoothDevice device = mCachedDevice; + String name = device.getName(); + if (TextUtils.isEmpty(name)) { + name = context.getString(R.string.bluetooth_device); + } + int disconnectMessage = profile.getDisconnectResource(); + if (disconnectMessage == 0) { + Log.w(TAG, "askDisconnect: unexpected profile " + profile); + disconnectMessage = R.string.bluetooth_disconnect_blank; } + String message = context.getString(disconnectMessage, name); + + DialogInterface.OnClickListener disconnectListener = + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + device.disconnect(profile); + } + }; + + mDisconnectDialog = Utils.showDisconnectDialog(context, + mDisconnectDialog, disconnectListener, name, message); } public void onDeviceAttributesChanged() { @@ -242,7 +268,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment // transaction.setBreadCrumbTitle(deviceName); // transaction.commit(); - findPreference(KEY_TITLE).setTitle(getResources().getString( + findPreference(KEY_TITLE).setTitle(getString( R.string.bluetooth_device_advanced_title, deviceName)); mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE); @@ -253,7 +279,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } private void refreshProfiles() { - for (Profile profile : mCachedDevice.getConnectableProfiles()) { + for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) { Preference profilePref = findPreference(profile.toString()); if (profilePref == null) { profilePref = createProfilePreference(profile); @@ -264,78 +290,43 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } } - private void refreshProfilePreference(Preference profilePref, Profile profile) { + private void refreshProfilePreference(Preference profilePref, LocalBluetoothProfile profile) { BluetoothDevice device = mCachedDevice.getDevice(); - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mManager, profile); - - int connectionStatus = profileManager.getConnectionStatus(device); /* * Gray out checkbox while connecting and disconnecting */ profilePref.setEnabled(!mCachedDevice.isBusy()); - profilePref.setSummary(getProfileSummary(profileManager, profile, device, - connectionStatus, isDeviceOnline())); - // TODO: - //profilePref.setChecked(profileManager.isPreferred(device)); + profilePref.setSummary(profile.getSummaryResourceForDevice(device)); } - private Profile getProfileOf(Preference pref) { - if (!(pref instanceof CheckBoxPreference)) return null; + private LocalBluetoothProfile 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 mProfileManager.getProfileByName(pref.getKey()); + } catch (IllegalArgumentException ignored) { return null; } } - private static int getProfileSummary(LocalBluetoothProfileManager profileManager, - Profile profile, BluetoothDevice device, int connectionStatus, boolean onlineMode) { - if (!onlineMode || connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) { - return getProfileSummaryForSettingPreference(profile); - } else { - return profileManager.getSummary(device); - } - } - - /** - * 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; - case HID: - return R.string.bluetooth_hid_profile_summary_use_for; - case PAN: - return R.string.bluetooth_pan_profile_summary_use_for; - default: - return 0; - } - } - public void onClick(View v) { - if (v.getTag() instanceof Profile) { - Profile prof = (Profile) v.getTag(); - CheckBoxPreference autoConnectPref = mAutoConnectPrefs.get(prof.toString()); + if (v.getTag() instanceof LocalBluetoothProfile) { + LocalBluetoothProfile prof = (LocalBluetoothProfile) v.getTag(); + CheckBoxPreference autoConnectPref = mAutoConnectPrefs.get(prof); if (autoConnectPref == null) { autoConnectPref = new CheckBoxPreference(getActivity()); autoConnectPref.setLayoutResource(com.android.internal.R.layout.preference_child); autoConnectPref.setKey(prof.toString()); autoConnectPref.setTitle(R.string.bluetooth_auto_connect); - autoConnectPref.setOrder(getProfilePreferenceIndex(prof) + 1); + autoConnectPref.setOrder(getProfilePreferenceIndex(prof.getOrdinal()) + 1); autoConnectPref.setChecked(getAutoConnect(prof)); autoConnectPref.setOnPreferenceChangeListener(this); - mAutoConnectPrefs.put(prof.name(), autoConnectPref); + mAutoConnectPrefs.put(prof, autoConnectPref); } BluetoothProfilePreference profilePref = (BluetoothProfilePreference) findPreference(prof.toString()); @@ -349,19 +340,14 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } } - private int getProfilePreferenceIndex(Profile prof) { - return mProfileContainer.getOrder() + prof.ordinal() * 10; + private int getProfilePreferenceIndex(int profIndex) { + return mProfileContainer.getOrder() + profIndex * 10; } private void unpairDevice() { mCachedDevice.unpair(); } - private boolean isDeviceOnline() { - // TODO: Verify - return mCachedDevice.isConnected() || mCachedDevice.isBusy(); - } - private void setIncomingFileTransfersAllowed(boolean allow) { // TODO: make an IPC call into BluetoothOpp to update Log.d(TAG, "Set allow incoming = " + allow); @@ -372,8 +358,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment return true; } - private boolean getAutoConnect(Profile prof) { - return LocalBluetoothProfileManager.getProfileManager(mManager, prof) - .isPreferred(mCachedDevice.getDevice()); + private boolean getAutoConnect(LocalBluetoothProfile prof) { + return prof.isPreferred(mCachedDevice.getDevice()); } } diff --git a/src/com/android/settings/bluetooth/DockEventReceiver.java b/src/com/android/settings/bluetooth/DockEventReceiver.java index 041099813..6ecbef63a 100644 --- a/src/com/android/settings/bluetooth/DockEventReceiver.java +++ b/src/com/android/settings/bluetooth/DockEventReceiver.java @@ -28,7 +28,7 @@ import android.content.Intent; import android.os.PowerManager; import android.util.Log; -public class DockEventReceiver extends BroadcastReceiver { +public final class DockEventReceiver extends BroadcastReceiver { private static final boolean DEBUG = DockService.DEBUG; @@ -39,11 +39,9 @@ public class DockEventReceiver extends BroadcastReceiver { private static final int EXTRA_INVALID = -1234; - private static final Object mStartingServiceSync = new Object(); + private static final Object sStartingServiceSync = new Object(); - private static final long WAKELOCK_TIMEOUT = 5000; - - private static PowerManager.WakeLock mStartingService; + private static PowerManager.WakeLock sStartingService; @Override public void onReceive(Context context, Intent intent) { @@ -75,7 +73,7 @@ public class DockEventReceiver extends BroadcastReceiver { beginStartingService(context, i); break; default: - if (DEBUG) Log.e(TAG, "Unknown state"); + Log.e(TAG, "Unknown state: " + state); break; } } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction()) || @@ -118,15 +116,15 @@ public class DockEventReceiver extends BroadcastReceiver { * Start the service to process the current event notifications, acquiring * the wake lock before returning to ensure that the service will run. */ - public static void beginStartingService(Context context, Intent intent) { - synchronized (mStartingServiceSync) { - if (mStartingService == null) { + private static void beginStartingService(Context context, Intent intent) { + synchronized (sStartingServiceSync) { + if (sStartingService == null) { PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + sStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StartingDockService"); } - mStartingService.acquire(WAKELOCK_TIMEOUT); + sStartingService.acquire(); if (context.startService(intent) == null) { Log.e(TAG, "Can't start DockService"); @@ -139,10 +137,13 @@ public class DockEventReceiver extends BroadcastReceiver { * releasing the wake lock if the service is now stopping. */ public static void finishStartingService(Service service, int startId) { - synchronized (mStartingServiceSync) { - if (mStartingService != null) { - if (DEBUG) Log.d(TAG, "stopSelf id = "+ startId); - service.stopSelfResult(startId); + synchronized (sStartingServiceSync) { + if (sStartingService != null) { + if (DEBUG) Log.d(TAG, "stopSelf id = " + startId); + if (service.stopSelfResult(startId)) { + Log.d(TAG, "finishStartingService: stopping service"); + sStartingService.release(); + } } } } diff --git a/src/com/android/settings/bluetooth/DockService.java b/src/com/android/settings/bluetooth/DockService.java index 810465238..b45770658 100644 --- a/src/com/android/settings/bluetooth/DockService.java +++ b/src/com/android/settings/bluetooth/DockService.java @@ -17,7 +17,6 @@ package com.android.settings.bluetooth; import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; import com.android.settings.bluetooth.LocalBluetoothProfileManager.ServiceListener; import android.app.AlertDialog; @@ -27,7 +26,7 @@ import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; -import android.content.Context; +import android.bluetooth.BluetoothProfile; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; @@ -44,12 +43,11 @@ import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; +import java.util.Collection; import java.util.List; import java.util.Set; -public class DockService extends Service implements AlertDialog.OnMultiChoiceClickListener, - DialogInterface.OnClickListener, DialogInterface.OnDismissListener, - CompoundButton.OnCheckedChangeListener, ServiceListener { +public final class DockService extends Service implements ServiceListener { private static final String TAG = "DockService"; @@ -82,14 +80,11 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli private static final String SHARED_PREFERENCES_NAME = "dock_settings"; - private static final String SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED = - "disable_bt_when_undock"; + private static final String KEY_DISABLE_BT_WHEN_UNDOCKED = "disable_bt_when_undock"; - private static final String SHARED_PREFERENCES_KEY_DISABLE_BT = - "disable_bt"; + private static final String KEY_DISABLE_BT = "disable_bt"; - private static final String SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT = - "connect_retry_count"; + private static final String KEY_CONNECT_RETRY_COUNT = "connect_retry_count"; /* * If disconnected unexpectedly, reconnect up to 6 times. Each profile counts @@ -103,8 +98,9 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private Runnable mRunnable; - private DockService mContext; - private LocalBluetoothManager mBtManager; + private LocalBluetoothAdapter mLocalAdapter; + private CachedBluetoothDeviceManager mDeviceManager; + private LocalBluetoothProfileManager mProfileManager; // Normally set after getting a docked event and unset when the connection // is severed. One exception is that mDevice could be null if the service @@ -113,7 +109,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli // Created and used for the duration of the dialog private AlertDialog mDialog; - private Profile[] mProfiles; + private LocalBluetoothProfile[] mProfiles; private boolean[] mCheckedItems; private int mStartIdAssociatedWithDialog; @@ -127,8 +123,19 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli public void onCreate() { if (DEBUG) Log.d(TAG, "onCreate"); - mBtManager = LocalBluetoothManager.getInstance(this); - mContext = this; + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Can't get LocalBluetoothManager: exiting"); + return; + } + + mLocalAdapter = manager.getBluetoothAdapter(); + mDeviceManager = manager.getCachedDeviceManager(); + mProfileManager = manager.getProfileManager(); + if (mProfileManager == null) { + Log.e(TAG, "Can't get LocalBluetoothProfileManager: exiting"); + return; + } HandlerThread thread = new HandlerThread("DockService"); thread.start(); @@ -141,12 +148,22 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli public void onDestroy() { if (DEBUG) Log.d(TAG, "onDestroy"); mRunnable = null; - LocalBluetoothProfileManager.removeServiceListener(this); if (mDialog != null) { mDialog.dismiss(); mDialog = null; } - mServiceLooper.quit(); + if (mProfileManager != null) { + mProfileManager.removeServiceListener(this); + } + if (mServiceLooper != null) { + mServiceLooper.quit(); + } + + mLocalAdapter = null; + mDeviceManager = null; + mProfileManager = null; + mServiceLooper = null; + mServiceHandler = null; } @Override @@ -155,9 +172,13 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return null; } + private SharedPreferences getPrefs() { + return getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE); + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (DEBUG) Log.d(TAG, "onStartCommand startId:" + startId + " flags: " + flags); + if (DEBUG) Log.d(TAG, "onStartCommand startId: " + startId + " flags: " + flags); if (intent == null) { // Nothing to process, stop. @@ -178,24 +199,24 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli * This assumes that the intent sender has checked that this is a dock * and that the intent is for a disconnect */ + final SharedPreferences prefs = getPrefs(); if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { BluetoothDevice disconnectedDevice = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - - int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0); + int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0); if (retryCount < MAX_CONNECT_RETRY) { - setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1); - handleUnexpectedDisconnect(disconnectedDevice, Profile.HEADSET, startId); + prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply(); + handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getHeadsetProfile(), startId); } return START_NOT_STICKY; } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { BluetoothDevice disconnectedDevice = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0); + int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0); if (retryCount < MAX_CONNECT_RETRY) { - setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1); - handleUnexpectedDisconnect(disconnectedDevice, Profile.A2DP, startId); + prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply(); + handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getA2dpProfile(), startId); } return START_NOT_STICKY; } @@ -209,7 +230,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } if (msg.what == MSG_TYPE_DOCKED) { - removeSetting(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT); + prefs.edit().remove(KEY_CONNECT_RETRY_COUNT).apply(); } msg.arg2 = startId; @@ -219,7 +240,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { + private ServiceHandler(Looper looper) { super(looper); } @@ -234,7 +255,6 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli int msgType = msg.what; final int state = msg.arg1; final int startId = msg.arg2; - boolean deferFinishCall = false; BluetoothDevice device = null; if (msg.obj != null) { device = (BluetoothDevice) msg.obj; @@ -243,102 +263,27 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli if(DEBUG) Log.d(TAG, "processMessage: " + msgType + " state: " + state + " device = " + (device == null ? "null" : device.toString())); + boolean deferFinishCall = false; + switch (msgType) { case MSG_TYPE_SHOW_UI: - if (mDialog != null) { - // Shouldn't normally happen - mDialog.dismiss(); - mDialog = null; - } - mDevice = device; - createDialog(mContext, mDevice, state, startId); + createDialog(device, state, startId); break; case MSG_TYPE_DOCKED: - if (DEBUG) { - // TODO figure out why hasMsg always returns false if device - // is supplied - Log.d(TAG, "1 Has undock perm msg = " - + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, mDevice)); - Log.d(TAG, "2 Has undock perm msg = " - + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, device)); - } - - mServiceHandler.removeMessages(MSG_TYPE_UNDOCKED_PERMANENT); - mServiceHandler.removeMessages(MSG_TYPE_DISABLE_BT); - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT); - - if (!device.equals(mDevice)) { - if (mDevice != null) { - // Not expected. Cleanup/undock existing - handleUndocked(mContext, mBtManager, mDevice); - } - - mDevice = device; - - // Register first in case LocalBluetoothProfileManager - // becomes ready after isManagerReady is called and it - // would be too late to register a service listener. - LocalBluetoothProfileManager.addServiceListener(this); - if (LocalBluetoothProfileManager.isManagerReady()) { - handleDocked(device, state, startId); - // Not needed after all - LocalBluetoothProfileManager.removeServiceListener(this); - } else { - final BluetoothDevice d = device; - mRunnable = new Runnable() { - public void run() { - handleDocked(d, state, startId); - } - }; - deferFinishCall = true; - } - } + deferFinishCall = msgTypeDocked(device, state, startId); break; case MSG_TYPE_UNDOCKED_PERMANENT: - // Grace period passed. Disconnect. - handleUndocked(mContext, mBtManager, device); - - if (DEBUG) { - Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = " - + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); - } - - if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)) { - // BT was disabled when we first docked - if (!hasOtherConnectedDevices(device)) { - if(DEBUG) Log.d(TAG, "QUEUED BT DISABLE"); - // Queue a delayed msg to disable BT - Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_DISABLE_BT, 0, - startId, null); - mServiceHandler.sendMessageDelayed(newMsg, DISABLE_BT_GRACE_PERIOD); - deferFinishCall = true; - } else { - // Don't disable BT if something is connected - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED); - } - } + deferFinishCall = msgTypeUndockedPermanent(device, startId); break; case MSG_TYPE_UNDOCKED_TEMPORARY: - // Undocked event received. Queue a delayed msg to sever connection - Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_UNDOCKED_PERMANENT, state, - startId, device); - mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD); + msgTypeUndockedTemporary(device, state, startId); break; case MSG_TYPE_DISABLE_BT: - if(DEBUG) Log.d(TAG, "BT DISABLE"); - if (mBtManager.getBluetoothAdapter().disable()) { - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED); - } else { - // disable() returned an error. Persist a flag to disable BT later - setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT, true); - mPendingTurnOffStartId = startId; - deferFinishCall = true; - if(DEBUG) Log.d(TAG, "disable failed. try again later " + startId); - } + deferFinishCall = msgTypeDisableBluetooth(startId); break; } @@ -346,24 +291,127 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli && !deferFinishCall) { // NOTE: We MUST not call stopSelf() directly, since we need to // make sure the wake lock acquired by the Receiver is released. - DockEventReceiver.finishStartingService(DockService.this, startId); + DockEventReceiver.finishStartingService(this, startId); } } - public synchronized boolean hasOtherConnectedDevices(BluetoothDevice dock) { - List<CachedBluetoothDevice> cachedDevices = mBtManager.getCachedDeviceManager() - .getCachedDevicesCopy(); - Set<BluetoothDevice> btDevices = mBtManager.getBluetoothAdapter().getBondedDevices(); - if (btDevices == null || cachedDevices == null || btDevices.size() == 0) { + private boolean msgTypeDisableBluetooth(int startId) { + if (DEBUG) { + Log.d(TAG, "BT DISABLE"); + } + final SharedPreferences prefs = getPrefs(); + if (mLocalAdapter.disable()) { + prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); return false; + } else { + // disable() returned an error. Persist a flag to disable BT later + prefs.edit().putBoolean(KEY_DISABLE_BT, true).apply(); + mPendingTurnOffStartId = startId; + if(DEBUG) { + Log.d(TAG, "disable failed. try again later " + startId); + } + return true; } - if(DEBUG) Log.d(TAG, "btDevices = " + btDevices.size()); - if(DEBUG) Log.d(TAG, "cachedDevices = " + cachedDevices.size()); + } - for (CachedBluetoothDevice device : cachedDevices) { - BluetoothDevice btDevice = device.getDevice(); - if (!btDevice.equals(dock) && btDevices.contains(btDevice) && device.isConnected()) { - if(DEBUG) Log.d(TAG, "connected device = " + device.getName()); + private void msgTypeUndockedTemporary(BluetoothDevice device, int state, + int startId) { + // Undocked event received. Queue a delayed msg to sever connection + Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_UNDOCKED_PERMANENT, state, + startId, device); + mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD); + } + + private boolean msgTypeUndockedPermanent(BluetoothDevice device, int startId) { + // Grace period passed. Disconnect. + handleUndocked(device); + final SharedPreferences prefs = getPrefs(); + + if (DEBUG) { + Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = " + + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)); + } + + if (prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)) { + if (hasOtherConnectedDevices(device)) { + // Don't disable BT if something is connected + prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); + } else { + // BT was disabled when we first docked + if (DEBUG) { + Log.d(TAG, "QUEUED BT DISABLE"); + } + // Queue a delayed msg to disable BT + Message newMsg = mServiceHandler.obtainMessage( + MSG_TYPE_DISABLE_BT, 0, startId, null); + mServiceHandler.sendMessageDelayed(newMsg, + DISABLE_BT_GRACE_PERIOD); + return true; + } + } + return false; + } + + private boolean msgTypeDocked(BluetoothDevice device, final int state, + final int startId) { + if (DEBUG) { + // TODO figure out why hasMsg always returns false if device + // is supplied + Log.d(TAG, "1 Has undock perm msg = " + + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, mDevice)); + Log.d(TAG, "2 Has undock perm msg = " + + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, device)); + } + + mServiceHandler.removeMessages(MSG_TYPE_UNDOCKED_PERMANENT); + mServiceHandler.removeMessages(MSG_TYPE_DISABLE_BT); + getPrefs().edit().remove(KEY_DISABLE_BT).apply(); + + if (device != null && !device.equals(mDevice)) { + if (mDevice != null) { + // Not expected. Cleanup/undock existing + handleUndocked(mDevice); + } + + mDevice = device; + + // Register first in case LocalBluetoothProfileManager + // becomes ready after isManagerReady is called and it + // would be too late to register a service listener. + mProfileManager.addServiceListener(this); + if (mProfileManager.isManagerReady()) { + handleDocked(device, state, startId); + // Not needed after all + mProfileManager.removeServiceListener(this); + } else { + final BluetoothDevice d = device; + mRunnable = new Runnable() { + public void run() { + handleDocked(d, state, startId); // FIXME: WTF runnable here? + } + }; + return true; + } + } + return false; + } + + synchronized boolean hasOtherConnectedDevices(BluetoothDevice dock) { + Collection<CachedBluetoothDevice> cachedDevices = mDeviceManager.getCachedDevicesCopy(); + Set<BluetoothDevice> btDevices = mLocalAdapter.getBondedDevices(); + if (btDevices == null || cachedDevices == null || btDevices.isEmpty()) { + return false; + } + if(DEBUG) { + Log.d(TAG, "btDevices = " + btDevices.size()); + Log.d(TAG, "cachedDeviceUIs = " + cachedDevices.size()); + } + + for (CachedBluetoothDevice deviceUI : cachedDevices) { + BluetoothDevice btDevice = deviceUI.getDevice(); + if (!btDevice.equals(dock) && btDevices.contains(btDevice) && deviceUI + .isConnected()) { + if(DEBUG) Log.d(TAG, "connected deviceUI = " + deviceUI.getName()); return true; } } @@ -404,96 +452,128 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return mServiceHandler.obtainMessage(msgType, state, 0, device); } - private boolean createDialog(DockService service, BluetoothDevice device, int state, - int startId) { + private void createDialog(BluetoothDevice device, + int state, int startId) { + if (mDialog != null) { + // Shouldn't normally happen + mDialog.dismiss(); + mDialog = null; + } + mDevice = device; switch (state) { case Intent.EXTRA_DOCK_STATE_CAR: case Intent.EXTRA_DOCK_STATE_DESK: break; default: - return false; + return; } startForeground(0, new Notification()); // Device in a new dock. - boolean firstTime = !mBtManager.hasDockAutoConnectSetting(device.getAddress()); + boolean firstTime = !LocalBluetoothPreferences.hasDockAutoConnectSetting(this, device.getAddress()); - CharSequence[] items = initBtSettings(service, device, state, firstTime); + CharSequence[] items = initBtSettings(device, state, firstTime); - final AlertDialog.Builder ab = new AlertDialog.Builder(service); - ab.setTitle(service.getString(R.string.bluetooth_dock_settings_title)); + final AlertDialog.Builder ab = new AlertDialog.Builder(this); + ab.setTitle(getString(R.string.bluetooth_dock_settings_title)); // Profiles - ab.setMultiChoiceItems(items, mCheckedItems, service); + ab.setMultiChoiceItems(items, mCheckedItems, mMultiClickListener); // Remember this settings - LayoutInflater inflater = (LayoutInflater) service - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - float pixelScaleFactor = service.getResources().getDisplayMetrics().density; + LayoutInflater inflater = (LayoutInflater) + getSystemService(LAYOUT_INFLATER_SERVICE); + float pixelScaleFactor = getResources().getDisplayMetrics().density; View view = inflater.inflate(R.layout.remember_dock_setting, null); CheckBox rememberCheckbox = (CheckBox) view.findViewById(R.id.remember); // check "Remember setting" by default if no value was saved - boolean checked = firstTime || mBtManager.getDockAutoConnectSetting(device.getAddress()); + boolean checked = firstTime || LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress()); rememberCheckbox.setChecked(checked); - rememberCheckbox.setOnCheckedChangeListener(this); + rememberCheckbox.setOnCheckedChangeListener(mCheckedChangeListener); int viewSpacingLeft = (int) (14 * pixelScaleFactor); int viewSpacingRight = (int) (14 * pixelScaleFactor); ab.setView(view, viewSpacingLeft, 0 /* top */, viewSpacingRight, 0 /* bottom */); if (DEBUG) { Log.d(TAG, "Auto connect = " - + mBtManager.getDockAutoConnectSetting(device.getAddress())); + + LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress())); } // Ok Button - ab.setPositiveButton(service.getString(android.R.string.ok), service); + ab.setPositiveButton(getString(android.R.string.ok), mClickListener); mStartIdAssociatedWithDialog = startId; mDialog = ab.create(); mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - mDialog.setOnDismissListener(service); + mDialog.setOnDismissListener(mDismissListener); mDialog.show(); - return true; } // Called when the individual bt profiles are clicked. - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - if (DEBUG) Log.d(TAG, "Item " + which + " changed to " + isChecked); - mCheckedItems[which] = isChecked; - } + private final DialogInterface.OnMultiChoiceClickListener mMultiClickListener = + new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + if (DEBUG) { + Log.d(TAG, "Item " + which + " changed to " + isChecked); + } + mCheckedItems[which] = isChecked; + } + }; + // Called when the "Remember" Checkbox is clicked - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (DEBUG) Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked); - if (mDevice != null) { - mBtManager.saveDockAutoConnectSetting(mDevice.getAddress(), isChecked); - } - } + private final CompoundButton.OnCheckedChangeListener mCheckedChangeListener = + new CompoundButton.OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (DEBUG) { + Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked); + } + if (mDevice != null) { + LocalBluetoothPreferences.saveDockAutoConnectSetting( + DockService.this, mDevice.getAddress(), isChecked); + } + } + }; + // Called when the dialog is dismissed - public void onDismiss(DialogInterface dialog) { - // NOTE: We MUST not call stopSelf() directly, since we need to - // make sure the wake lock acquired by the Receiver is released. - if (mPendingDevice == null) { - DockEventReceiver.finishStartingService(mContext, mStartIdAssociatedWithDialog); - } - mContext.stopForeground(true); - } + private final DialogInterface.OnDismissListener mDismissListener = + new DialogInterface.OnDismissListener() { + public void onDismiss(DialogInterface dialog) { + // NOTE: We MUST not call stopSelf() directly, since we need to + // make sure the wake lock acquired by the Receiver is released. + if (mPendingDevice == null) { + DockEventReceiver.finishStartingService( + DockService.this, mStartIdAssociatedWithDialog); + } + stopForeground(true); + } + }; // Called when clicked on the OK button - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE && mDevice != null) { - if (!mBtManager.hasDockAutoConnectSetting(mDevice.getAddress())) { - mBtManager.saveDockAutoConnectSetting(mDevice.getAddress(), true); - } + private final DialogInterface.OnClickListener mClickListener = + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE + && mDevice != null) { + if (!LocalBluetoothPreferences + .hasDockAutoConnectSetting( + DockService.this, + mDevice.getAddress())) { + LocalBluetoothPreferences + .saveDockAutoConnectSetting( + DockService.this, + mDevice.getAddress(), true); + } - applyBtSettings(mDevice, mStartIdAssociatedWithDialog); - } - } + applyBtSettings(mDevice, mStartIdAssociatedWithDialog); + } + } + }; - private CharSequence[] initBtSettings(DockService service, BluetoothDevice device, int state, - boolean firstTime) { + private CharSequence[] initBtSettings(BluetoothDevice device, + int state, boolean firstTime) { // TODO Avoid hardcoding dock and profiles. Read from system properties int numOfProfiles; switch (state) { @@ -507,96 +587,54 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return null; } - mProfiles = new Profile[numOfProfiles]; + mProfiles = new LocalBluetoothProfile[numOfProfiles]; mCheckedItems = new boolean[numOfProfiles]; CharSequence[] items = new CharSequence[numOfProfiles]; + // FIXME: convert switch to something else switch (state) { case Intent.EXTRA_DOCK_STATE_CAR: - items[0] = service.getString(R.string.bluetooth_dock_settings_headset); - items[1] = service.getString(R.string.bluetooth_dock_settings_a2dp); - mProfiles[0] = Profile.HEADSET; - mProfiles[1] = Profile.A2DP; + items[0] = getString(R.string.bluetooth_dock_settings_headset); + items[1] = getString(R.string.bluetooth_dock_settings_a2dp); + mProfiles[0] = mProfileManager.getHeadsetProfile(); + mProfiles[1] = mProfileManager.getA2dpProfile(); if (firstTime) { // Enable by default for car dock mCheckedItems[0] = true; mCheckedItems[1] = true; } else { - mCheckedItems[0] = LocalBluetoothProfileManager.getProfileManager(mBtManager, - Profile.HEADSET).isPreferred(device); - mCheckedItems[1] = LocalBluetoothProfileManager.getProfileManager(mBtManager, - Profile.A2DP).isPreferred(device); + mCheckedItems[0] = mProfiles[0].isPreferred(device); + mCheckedItems[1] = mProfiles[1].isPreferred(device); } break; case Intent.EXTRA_DOCK_STATE_DESK: - items[0] = service.getString(R.string.bluetooth_dock_settings_a2dp); - mProfiles[0] = Profile.A2DP; + items[0] = getString(R.string.bluetooth_dock_settings_a2dp); + mProfiles[0] = mProfileManager.getA2dpProfile(); if (firstTime) { // Disable by default for desk dock mCheckedItems[0] = false; } else { - mCheckedItems[0] = LocalBluetoothProfileManager.getProfileManager(mBtManager, - Profile.A2DP).isPreferred(device); + mCheckedItems[0] = mProfiles[0].isPreferred(device); } break; } return items; } + // TODO: move to background thread to fix strict mode warnings private void handleBtStateChange(Intent intent, int startId) { int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); synchronized (this) { if(DEBUG) Log.d(TAG, "BtState = " + btState + " mPendingDevice = " + mPendingDevice); if (btState == BluetoothAdapter.STATE_ON) { - if (mPendingDevice != null) { - if (mPendingDevice.equals(mDevice)) { - if(DEBUG) Log.d(TAG, "applying settings"); - applyBtSettings(mPendingDevice, mPendingStartId); - } else if(DEBUG) { - Log.d(TAG, "mPendingDevice (" + mPendingDevice + ") != mDevice (" - + mDevice + ")"); - } - - mPendingDevice = null; - DockEventReceiver.finishStartingService(mContext, mPendingStartId); - } else { - if (DEBUG) { - Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = " - + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); - } - // Reconnect if docked and bluetooth was enabled by user. - Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice device = i - .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device != null) { - connectIfEnabled(device); - } - } else if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT) - && mBtManager.getBluetoothAdapter().disable()) { - mPendingTurnOffStartId = startId; - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT); - return; - } - } - } - - if (mPendingTurnOnStartId != INVALID_STARTID) { - DockEventReceiver.finishStartingService(this, mPendingTurnOnStartId); - mPendingTurnOnStartId = INVALID_STARTID; - } - - DockEventReceiver.finishStartingService(this, startId); + handleBluetoothStateOn(startId); } else if (btState == BluetoothAdapter.STATE_TURNING_OFF) { // Remove the flag to disable BT if someone is turning off bt. // The rational is that: // a) if BT is off at undock time, no work needs to be done // b) if BT is on at undock time, the user wants it on. - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED); + getPrefs().edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); DockEventReceiver.finishStartingService(this, startId); } else if (btState == BluetoothAdapter.STATE_OFF) { // Bluetooth was turning off as we were trying to turn it on. @@ -605,12 +643,12 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli if (mPendingTurnOffStartId != INVALID_STARTID) { DockEventReceiver.finishStartingService(this, mPendingTurnOffStartId); - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT); + getPrefs().edit().remove(KEY_DISABLE_BT).apply(); mPendingTurnOffStartId = INVALID_STARTID; } if (mPendingDevice != null) { - mBtManager.getBluetoothAdapter().enable(); + mLocalAdapter.enable(); mPendingTurnOnStartId = startId; } else { DockEventReceiver.finishStartingService(this, startId); @@ -619,86 +657,124 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } } - private void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice, Profile profile, - int startId) { - synchronized (this) { - if (DEBUG) Log.d(TAG, "handling failed connect for " + disconnectedDevice); + private void handleBluetoothStateOn(int startId) { + if (mPendingDevice != null) { + if (mPendingDevice.equals(mDevice)) { + if(DEBUG) { + Log.d(TAG, "applying settings"); + } + applyBtSettings(mPendingDevice, mPendingStartId); + } else if(DEBUG) { + Log.d(TAG, "mPendingDevice (" + mPendingDevice + ") != mDevice (" + + mDevice + ')'); + } + + mPendingDevice = null; + DockEventReceiver.finishStartingService(this, mPendingStartId); + } else { + final SharedPreferences prefs = getPrefs(); + if (DEBUG) { + Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = " + + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)); + } + // Reconnect if docked and bluetooth was enabled by user. + Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (i != null) { + int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + BluetoothDevice device = i + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device != null) { + connectIfEnabled(device); + } + } else if (prefs.getBoolean(KEY_DISABLE_BT, false) + && mLocalAdapter.disable()) { + mPendingTurnOffStartId = startId; + prefs.edit().remove(KEY_DISABLE_BT).apply(); + return; + } + } + } + + if (mPendingTurnOnStartId != INVALID_STARTID) { + DockEventReceiver.finishStartingService(this, mPendingTurnOnStartId); + mPendingTurnOnStartId = INVALID_STARTID; + } + + DockEventReceiver.finishStartingService(this, startId); + } + + private synchronized void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice, + LocalBluetoothProfile profile, int startId) { + if (DEBUG) { + Log.d(TAG, "handling failed connect for " + disconnectedDevice); + } // Reconnect if docked. if (disconnectedDevice != null) { // registerReceiver can't be called from a BroadcastReceiver - Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent intent = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (intent != null) { + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice dockedDevice = i + BluetoothDevice dockedDevice = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (dockedDevice != null && dockedDevice.equals(disconnectedDevice)) { - CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, - mBtManager, dockedDevice); - cachedDevice.connect(profile); + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( + dockedDevice); + cachedDevice.connectProfile(profile); } } } } DockEventReceiver.finishStartingService(this, startId); - } } private synchronized void connectIfEnabled(BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager, device); - List<Profile> profiles = cachedDevice.getConnectableProfiles(); - for (int i = 0; i < profiles.size(); i++) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mBtManager, profiles.get(i)); - int auto; - if (Profile.A2DP == profiles.get(i)) { - auto = BluetoothA2dp.PRIORITY_AUTO_CONNECT; - } else if (Profile.HEADSET == profiles.get(i)) { - auto = BluetoothHeadset.PRIORITY_AUTO_CONNECT; - } else { - continue; - } - - if (profileManager.getPreferred(device) == auto) { + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( + device); + List<LocalBluetoothProfile> profiles = cachedDevice.getConnectableProfiles(); + for (LocalBluetoothProfile profile : profiles) { + if (profile.getPreferred(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { cachedDevice.connect(false); - break; + return; } } } - private synchronized void applyBtSettings(final BluetoothDevice device, int startId) { - if (device == null || mProfiles == null || mCheckedItems == null) + private synchronized void applyBtSettings(BluetoothDevice device, int startId) { + if (device == null || mProfiles == null || mCheckedItems == null + || mLocalAdapter == null) { return; + } // Turn on BT if something is enabled - synchronized (this) { - for (boolean enable : mCheckedItems) { - if (enable) { - int btState = mBtManager.getBluetoothState(); - if(DEBUG) Log.d(TAG, "BtState = " + btState); - // May have race condition as the phone comes in and out and in the dock. - // Always turn on BT - mBtManager.getBluetoothAdapter().enable(); - - switch (btState) { - case BluetoothAdapter.STATE_OFF: - case BluetoothAdapter.STATE_TURNING_OFF: - case BluetoothAdapter.STATE_TURNING_ON: - if (mPendingDevice != null && mPendingDevice.equals(mDevice)) { - return; - } - - mPendingDevice = device; - mPendingStartId = startId; - if (btState != BluetoothAdapter.STATE_TURNING_ON) { - setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED, - true); - } - return; + for (boolean enable : mCheckedItems) { + if (enable) { + int btState = mLocalAdapter.getBluetoothState(); + if (DEBUG) { + Log.d(TAG, "BtState = " + btState); + } + // May have race condition as the phone comes in and out and in the dock. + // Always turn on BT + mLocalAdapter.enable(); + + // if adapter was previously OFF, TURNING_OFF, or TURNING_ON + if (btState != BluetoothAdapter.STATE_ON) { + if (mPendingDevice != null && mPendingDevice.equals(mDevice)) { + return; + } + + mPendingDevice = device; + mPendingStartId = startId; + if (btState != BluetoothAdapter.STATE_TURNING_ON) { + getPrefs().edit().putBoolean( + KEY_DISABLE_BT_WHEN_UNDOCKED, true).apply(); } + return; } } } @@ -706,28 +782,26 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli mPendingDevice = null; boolean callConnect = false; - CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager, + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( device); for (int i = 0; i < mProfiles.length; i++) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mBtManager, mProfiles[i]); - - if (DEBUG) Log.d(TAG, mProfiles[i].toString() + " = " + mCheckedItems[i]); + LocalBluetoothProfile profile = mProfiles[i]; + if (DEBUG) Log.d(TAG, profile.toString() + " = " + mCheckedItems[i]); if (mCheckedItems[i]) { // Checked but not connected callConnect = true; } else if (!mCheckedItems[i]) { // Unchecked, may or may not be connected. - int status = profileManager.getConnectionStatus(cachedDevice.getDevice()); - if (SettingsBtStatus.isConnectionStatusConnected(status)) { + int status = profile.getConnectionStatus(cachedDevice.getDevice()); + if (status == BluetoothProfile.STATE_CONNECTED) { if (DEBUG) Log.d(TAG, "applyBtSettings - Disconnecting"); cachedDevice.disconnect(mProfiles[i]); } } - profileManager.setPreferred(device, mCheckedItems[i]); + profile.setPreferred(device, mCheckedItems[i]); if (DEBUG) { - if (mCheckedItems[i] != profileManager.isPreferred(device)) { + if (mCheckedItems[i] != profile.isPreferred(device)) { Log.e(TAG, "Can't save preferred value"); } } @@ -739,84 +813,47 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } } - private synchronized void handleDocked(final BluetoothDevice device, final int state, - final int startId) { - if (mBtManager.getDockAutoConnectSetting(device.getAddress())) { + private synchronized void handleDocked(BluetoothDevice device, int state, + int startId) { + if (LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress())) { // Setting == auto connect - initBtSettings(mContext, device, state, false); + initBtSettings(device, state, false); applyBtSettings(mDevice, startId); } else { - createDialog(mContext, device, state, startId); + createDialog(device, state, startId); } } - private synchronized void handleUndocked(Context context, LocalBluetoothManager localManager, - BluetoothDevice device) { + private synchronized void handleUndocked(BluetoothDevice device) { mRunnable = null; - LocalBluetoothProfileManager.removeServiceListener(this); + mProfileManager.removeServiceListener(this); if (mDialog != null) { mDialog.dismiss(); mDialog = null; } mDevice = null; mPendingDevice = null; - CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context, - localManager, device); - cachedBluetoothDevice.disconnect(); + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(device); + cachedDevice.disconnect(); } - private static CachedBluetoothDevice getCachedBluetoothDevice(Context context, - LocalBluetoothManager localManager, BluetoothDevice device) { - CachedBluetoothDeviceManager cachedDeviceManager = localManager.getCachedDeviceManager(); - CachedBluetoothDevice cachedBluetoothDevice = cachedDeviceManager.findDevice(device); - if (cachedBluetoothDevice == null) { - cachedBluetoothDevice = new CachedBluetoothDevice(context, device); + private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); } - return cachedBluetoothDevice; - } - - private boolean getSettingBool(String key) { - SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE); - return sharedPref.getBoolean(key, false); - } - - private int getSettingInt(String key, int defaultValue) { - SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE); - return sharedPref.getInt(key, defaultValue); - } - - private void setSettingBool(String key, boolean bool) { - SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE).edit(); - editor.putBoolean(key, bool); - editor.apply(); - } - - private void setSettingInt(String key, int value) { - SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE).edit(); - editor.putInt(key, value); - editor.apply(); - } - - private void removeSetting(String key) { - SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.remove(key); - editor.apply(); + return cachedDevice; } public synchronized void onServiceConnected() { if (mRunnable != null) { mRunnable.run(); mRunnable = null; - LocalBluetoothProfileManager.removeServiceListener(this); + mProfileManager.removeServiceListener(this); } } public void onServiceDisconnected() { + // FIXME: shouldn't I do something on service disconnected too? } } diff --git a/src/com/android/settings/bluetooth/HeadsetProfile.java b/src/com/android/settings/bluetooth/HeadsetProfile.java new file mode 100644 index 000000000..dac47b767 --- /dev/null +++ b/src/com/android/settings/bluetooth/HeadsetProfile.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.Handler; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settings.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * HeadsetProfile handles Bluetooth HFP and Headset profiles. + */ +final class HeadsetProfile implements LocalBluetoothProfile { + private static final String TAG = "HeadsetProfile"; + + private BluetoothHeadset mService; + private boolean mProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final ParcelUuid[] UUIDS = { + BluetoothUuid.HSP, + BluetoothUuid.Handsfree, + }; + + static final String NAME = "HEADSET"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 0; + + // These callbacks run on the main thread. + private final class HeadsetServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothHeadset) proxy; + mProfileReady = true; + // We just bound to the service, so refresh the UI of the + // headset device. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (deviceList.isEmpty()) { + return; + } + BluetoothDevice firstDevice = deviceList.get(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(firstDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HeadsetProfile found new device: " + firstDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, firstDevice); + } + device.onProfileStateChanged(HeadsetProfile.this, + BluetoothProfile.STATE_CONNECTED); + + mProfileManager.callServiceConnectedListeners(); + } + + public void onServiceDisconnected(int profile) { + mProfileReady = false; + mService = null; + mProfileManager.callServiceDisconnectedListeners(); + } + } + + // TODO(): The calls must get queued if mService becomes null. + // It can happen when the phone app crashes for some reason. + // All callers should have service listeners. Dock Service is the only + // one right now. + HeadsetProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(), + BluetoothProfile.HEADSET); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) { + // Downgrade priority as user is disconnecting the headset. + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } else { + return false; + } + } + + public int getConnectionStatus(BluetoothDevice device) { + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public synchronized boolean isProfileReady() { + return mProfileReady; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_headset; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_headset_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_headset_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_headset_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headset_hfp; + } +} diff --git a/src/com/android/settings/bluetooth/HidProfile.java b/src/com/android/settings/bluetooth/HidProfile.java new file mode 100644 index 000000000..9185059d6 --- /dev/null +++ b/src/com/android/settings/bluetooth/HidProfile.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settings.R; + +import java.util.List; + +/** + * HidProfile handles Bluetooth HID profile. + */ +final class HidProfile implements LocalBluetoothProfile { + private BluetoothInputDevice mService; + private boolean mProfileReady; + + static final String NAME = "HID"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 3; + + // These callbacks run on the main thread. + private final class InputDeviceServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothInputDevice) proxy; + mProfileReady = true; + } + + public void onServiceDisconnected(int profile) { + mProfileReady = false; + mService = null; + } + } + + HidProfile(Context context, LocalBluetoothAdapter adapter) { + adapter.getProfileProxy(context, new InputDeviceServiceListener(), + BluetoothProfile.INPUT_DEVICE); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public boolean isProfileReady() { + return mProfileReady; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_hid; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_hid_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_hid_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_hid_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + if (btClass == null) { + return R.drawable.ic_bt_keyboard_hid; + } + return getHidClassDrawable(btClass); + } + + static int getHidClassDrawable(BluetoothClass btClass) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.PERIPHERAL_KEYBOARD: + case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: + return R.drawable.ic_bt_keyboard_hid; + case BluetoothClass.Device.PERIPHERAL_POINTING: + return R.drawable.ic_bt_pointing_hid; + default: + return R.drawable.ic_bt_misc_hid; + } + } +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java b/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java new file mode 100644 index 000000000..013171c14 --- /dev/null +++ b/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import java.util.Set; + +/** + * LocalBluetoothAdapter provides an interface between the Settings app + * and the functionality of the local {@link BluetoothAdapter}, specifically + * those related to state transitions of the adapter itself. + * + * <p>Connection and bonding state changes affecting specific devices + * are handled by {@link CachedBluetoothDeviceManager}, + * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}. + */ +public final class LocalBluetoothAdapter { + private static final String TAG = "LocalBluetoothAdapter"; + + /** This class does not allow direct access to the BluetoothAdapter. */ + private final BluetoothAdapter mAdapter; + + private LocalBluetoothProfileManager mProfileManager; + + private static LocalBluetoothAdapter sInstance; + + private int mState = BluetoothAdapter.ERROR; + + private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins + + private long mLastScan; + + private LocalBluetoothAdapter(BluetoothAdapter adapter) { + mAdapter = adapter; + } + + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + /** + * Get the singleton instance of the LocalBluetoothAdapter. If this device + * doesn't support Bluetooth, then null will be returned. Callers must be + * prepared to handle a null return value. + * @return the LocalBluetoothAdapter object, or null if not supported + */ + static synchronized LocalBluetoothAdapter getInstance() { + if (sInstance == null) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + sInstance = new LocalBluetoothAdapter(adapter); + } + } + + return sInstance; + } + + // Pass-through BluetoothAdapter methods that we can intercept if necessary + + void cancelDiscovery() { + mAdapter.cancelDiscovery(); + } + + boolean enable() { + return mAdapter.enable(); + } + + boolean disable() { + return mAdapter.disable(); + } + + void getProfileProxy(Context context, + BluetoothProfile.ServiceListener listener, int profile) { + mAdapter.getProfileProxy(context, listener, profile); + } + + Set<BluetoothDevice> getBondedDevices() { + return mAdapter.getBondedDevices(); + } + + String getName() { + return mAdapter.getName(); + } + + int getScanMode() { + return mAdapter.getScanMode(); + } + + int getState() { + return mAdapter.getState(); + } + + ParcelUuid[] getUuids() { + return mAdapter.getUuids(); + } + + boolean isDiscovering() { + return mAdapter.isDiscovering(); + } + + boolean isEnabled() { + return mAdapter.isEnabled(); + } + + void setDiscoverableTimeout(int timeout) { + mAdapter.setDiscoverableTimeout(timeout); + } + + void setName(String name) { + mAdapter.setName(name); + } + + void setScanMode(int mode) { + mAdapter.setScanMode(mode); + } + + boolean setScanMode(int mode, int duration) { + return mAdapter.setScanMode(mode, duration); + } + + void startScanning(boolean force) { + // Only start if we're not already scanning + if (!mAdapter.isDiscovering()) { + if (!force) { + // Don't scan more than frequently than SCAN_EXPIRATION_MS, + // unless forced + if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) { + return; + } + + // If we are playing music, don't scan unless forced. + A2dpProfile a2dp = mProfileManager.getA2dpProfile(); + if (a2dp != null && a2dp.isA2dpPlaying()) { + return; + } + } + + if (mAdapter.startDiscovery()) { + mLastScan = System.currentTimeMillis(); + } + } + } + + void stopScanning() { + if (mAdapter.isDiscovering()) { + mAdapter.cancelDiscovery(); + } + } + + public synchronized int getBluetoothState() { + // Always sync state, in case it changed while paused + syncBluetoothState(); + return mState; + } + + synchronized void setBluetoothStateInt(int state) { + mState = state; + + if (state == BluetoothAdapter.STATE_ON) { + // if mProfileManager hasn't been constructed yet, it will + // get the adapter UUIDs in its constructor when it is. + if (mProfileManager != null) { + mProfileManager.setBluetoothStateOn(); + } + } + } + + // Returns true if the state changed; false otherwise. + boolean syncBluetoothState() { + int currentState = mAdapter.getState(); + if (currentState != mState) { + setBluetoothStateInt(mAdapter.getState()); + return true; + } + return false; + } + + public void setBluetoothEnabled(boolean enabled) { + boolean success = enabled + ? mAdapter.enable() + : mAdapter.disable(); + + if (success) { + setBluetoothStateInt(enabled + ? BluetoothAdapter.STATE_TURNING_ON + : BluetoothAdapter.STATE_TURNING_OFF); + } else { + if (Utils.V) { + Log.v(TAG, "setBluetoothEnabled call, manager didn't return " + + "success for enabled: " + enabled); + } + + syncBluetoothState(); + } + } +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java index f3c5e6f30..0c04e2273 100644 --- a/src/com/android/settings/bluetooth/LocalBluetoothManager.java +++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,376 +16,96 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - -import android.app.Activity; -import android.app.AlertDialog; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.content.SharedPreferences; -import android.os.ParcelUuid; -import android.util.Config; import android.util.Log; -import android.widget.Toast; - -import java.util.ArrayList; -import java.util.List; -// 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. + * the Bluetooth API. Note that {@link #getInstance} will return null + * if there is no Bluetooth adapter on this device, and callers must be + * prepared to handle this case. */ -public class LocalBluetoothManager { +public final class LocalBluetoothManager { private static final String TAG = "LocalBluetoothManager"; - static final boolean V = Config.LOGV; - static final boolean D = Config.LOGD; - - private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings"; /** Singleton instance. */ private static LocalBluetoothManager sInstance; - private Context mContext; - /** If a BT-related activity is in the foreground, this will be it. */ - private Activity mForegroundActivity; - private AlertDialog mErrorDialog = null; - - private BluetoothAdapter mAdapter; - - private CachedBluetoothDeviceManager mCachedDeviceManager; - private BluetoothA2dp mBluetoothA2dp; - - private int mState = BluetoothAdapter.ERROR; + private final Context mContext; - private final List<Callback> mCallbacks = new ArrayList<Callback>(); - - private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins - - // If a device was picked from the device picker or was in discoverable mode - // in the last 60 seconds, show the pairing dialogs in foreground instead - // of raising notifications - private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; - - public static final String SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP = - "last_discovering_time"; - - private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE = - "last_selected_device"; + /** If a BT-related activity is in the foreground, this will be it. */ + private Context mForegroundActivity; - private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME = - "last_selected_device_time"; + private final LocalBluetoothAdapter mLocalAdapter; - private static final String SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock"; + private final CachedBluetoothDeviceManager mCachedDeviceManager; - private long mLastScan; + /** The Bluetooth profile manager. */ + private final LocalBluetoothProfileManager mProfileManager; - private LocalBluetoothManager() { } + /** The broadcast receiver event manager. */ + private final BluetoothEventManager mEventManager; - public static LocalBluetoothManager getInstance(Context context) { - synchronized (LocalBluetoothManager.class) { - if (sInstance == null) { - sInstance = new LocalBluetoothManager(); - if (!sInstance.init(context)) { - return null; - } - LocalBluetoothProfileManager.init(sInstance); + public static synchronized LocalBluetoothManager getInstance(Context context) { + if (sInstance == null) { + LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance(); + if (adapter == null) { + return null; } - - return sInstance; - } - } - - private boolean init(Context context) { - // This will be around as long as this process is - mContext = context.getApplicationContext(); - - mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mAdapter == null) { - return false; + // This will be around as long as this process is + Context appContext = context.getApplicationContext(); + sInstance = new LocalBluetoothManager(adapter, appContext); } - mCachedDeviceManager = new CachedBluetoothDeviceManager(this); - - new BluetoothEventRedirector(this).registerReceiver(); + return sInstance; + } - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); + private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) { + mContext = context; + mLocalAdapter = adapter; - return true; + mCachedDeviceManager = new CachedBluetoothDeviceManager(); + mEventManager = new BluetoothEventManager(mLocalAdapter, + mCachedDeviceManager); + mProfileManager = new LocalBluetoothProfileManager(context, + mLocalAdapter, mCachedDeviceManager, mEventManager); } - private final BluetoothProfile.ServiceListener mProfileListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mBluetoothA2dp = (BluetoothA2dp) proxy; - } - public void onServiceDisconnected(int profile) { - mBluetoothA2dp = null; - } - }; - - public BluetoothAdapter getBluetoothAdapter() { - return mAdapter; + public LocalBluetoothAdapter getBluetoothAdapter() { + return mLocalAdapter; } public Context getContext() { return mContext; } - public Activity getForegroundActivity() { - return mForegroundActivity; - } - - public void setForegroundActivity(Activity activity) { - if (mErrorDialog != null) { - mErrorDialog.dismiss(); - mErrorDialog = null; - } - mForegroundActivity = activity; + boolean isForegroundActivity() { + return mForegroundActivity != null; } - public SharedPreferences getSharedPreferences() { - return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - } - - public CachedBluetoothDeviceManager getCachedDeviceManager() { - return mCachedDeviceManager; - } - - 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 (mAdapter.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); + synchronized void setForegroundActivity(Context context) { + if (context != null) { + Log.d(TAG, "setting foreground activity to non-null context"); + mForegroundActivity = context; + mEventManager.resume(context); } else { - if (!force) { - // Don't scan more than frequently than SCAN_EXPIRATION_MS, - // unless forced - if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) { - return; - } - - // If we are playing music, don't scan unless forced. - if (mBluetoothA2dp != null) { - List<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedDevices(); - if (sinks.size() > 0) { - if (mBluetoothA2dp.isA2dpPlaying(sinks.get(0))) return; - } - } + if (mForegroundActivity != null) { + Log.d(TAG, "setting foreground activity to null"); + mEventManager.pause(mForegroundActivity); + mForegroundActivity = null; } - - if (mAdapter.startDiscovery()) { - mLastScan = System.currentTimeMillis(); - } - } - } - - public void stopScanning() { - if (mAdapter.isDiscovering()) { - mAdapter.cancelDiscovery(); - } - } - - public int getBluetoothState() { - - if (mState == BluetoothAdapter.ERROR) { - syncBluetoothState(); } - - return mState; } - void setBluetoothStateInt(int state) { - mState = state; - - if (state == BluetoothAdapter.STATE_ON) { - ParcelUuid[] uuids = mAdapter.getUuids(); - LocalBluetoothProfileManager.updateLocalProfiles(getInstance(mContext), uuids); - } - - if (state == BluetoothAdapter.STATE_ON || - state == BluetoothAdapter.STATE_OFF) { - mCachedDeviceManager.onBluetoothStateChanged(state == - BluetoothAdapter.STATE_ON); - } - } - - private void syncBluetoothState() { - int bluetoothState; - - if (mAdapter != null) { - bluetoothState = mAdapter.isEnabled() - ? BluetoothAdapter.STATE_ON - : BluetoothAdapter.STATE_OFF; - } else { - bluetoothState = BluetoothAdapter.ERROR; - } - - setBluetoothStateInt(bluetoothState); - } - - public void setBluetoothEnabled(boolean enabled) { - boolean wasSetStateSuccessful = enabled - ? mAdapter.enable() - : mAdapter.disable(); - - if (wasSetStateSuccessful) { - setBluetoothStateInt(enabled - ? BluetoothAdapter.STATE_TURNING_ON - : BluetoothAdapter.STATE_TURNING_OFF); - } 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) - mCachedDeviceManager.onScanningStateChanged(started); - dispatchScanningStateChanged(started); - } - - private void dispatchScanningStateChanged(boolean started) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onScanningStateChanged(started); - } - } - } - - public void showError(BluetoothDevice device, int messageResId) { - CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device); - String name = null; - if (cachedDevice == null) { - if (device != null) name = device.getName(); - - if (name == null) { - name = mContext.getString(R.string.bluetooth_remote_device); - } - } else { - name = cachedDevice.getName(); - } - String message = mContext.getString(messageResId, name); - - if (mForegroundActivity != null) { - // Need an activity context to show a dialog - mErrorDialog = new AlertDialog.Builder(mForegroundActivity) - .setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(R.string.bluetooth_error_title) - .setMessage(message) - .setPositiveButton(android.R.string.ok, null) - .show(); - } else { - // Fallback on a toast - Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); - } - } - - public interface Callback { - void onScanningStateChanged(boolean started); - void onDeviceAdded(CachedBluetoothDevice cachedDevice); - void onDeviceDeleted(CachedBluetoothDevice cachedDevice); - void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState); - } - - public boolean shouldShowDialogInForeground(String deviceAddress) { - // If Bluetooth Settings is visible - if (mForegroundActivity != null) return true; - - long currentTimeMillis = System.currentTimeMillis(); - SharedPreferences sharedPreferences = getSharedPreferences(); - - // If the device was in discoverABLE mode recently - long lastDiscoverableEndTime = sharedPreferences.getLong( - BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0); - if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) - > currentTimeMillis) { - return true; - } - - // If the device was discoverING recently - if (mAdapter != null && mAdapter.isDiscovering()) { - return true; - } else if ((sharedPreferences.getLong(SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, 0) + - GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) { - return true; - } - - // If the device was picked in the device picker recently - if (deviceAddress != null) { - String lastSelectedDevice = sharedPreferences.getString( - SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, null); - - if (deviceAddress.equals(lastSelectedDevice)) { - long lastDeviceSelectedTime = sharedPreferences.getLong( - SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, 0); - if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) - > currentTimeMillis) { - return true; - } - } - } - return false; - } - - void persistSelectedDeviceInPicker(String deviceAddress) { - SharedPreferences.Editor editor = getSharedPreferences().edit(); - editor.putString(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, - deviceAddress); - editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, - System.currentTimeMillis()); - editor.apply(); - } - - public boolean hasDockAutoConnectSetting(String addr) { - return getSharedPreferences().contains(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr); - } - - public boolean getDockAutoConnectSetting(String addr) { - return getSharedPreferences().getBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr, - false); + CachedBluetoothDeviceManager getCachedDeviceManager() { + return mCachedDeviceManager; } - public void saveDockAutoConnectSetting(String addr, boolean autoConnect) { - SharedPreferences.Editor editor = getSharedPreferences().edit(); - editor.putBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr, autoConnect); - editor.apply(); + BluetoothEventManager getEventManager() { + return mEventManager; } - public void removeDockAutoConnectSetting(String addr) { - SharedPreferences.Editor editor = getSharedPreferences().edit(); - editor.remove(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr); - editor.apply(); + LocalBluetoothProfileManager getProfileManager() { + return mProfileManager; } } diff --git a/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java b/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java new file mode 100644 index 000000000..7e62b0e1a --- /dev/null +++ b/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java @@ -0,0 +1,157 @@ +/* + * 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.QueuedWork; +import android.content.Context; +import android.content.SharedPreferences; + +/** + * LocalBluetoothPreferences provides an interface to the preferences + * related to Bluetooth. + */ +final class LocalBluetoothPreferences { +// private static final String TAG = "LocalBluetoothPreferences"; + + private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings"; + + // If a device was picked from the device picker or was in discoverable mode + // in the last 60 seconds, show the pairing dialogs in foreground instead + // of raising notifications + private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; + + private static final String KEY_DISCOVERING_TIMESTAMP = "last_discovering_time"; + + private static final String KEY_LAST_SELECTED_DEVICE = "last_selected_device"; + + private static final String KEY_LAST_SELECTED_DEVICE_TIME = "last_selected_device_time"; + + private static final String KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock"; + + private static final String KEY_DISCOVERABLE_END_TIMESTAMP = "discoverable_end_timestamp"; + + private LocalBluetoothPreferences() { + } + + private static SharedPreferences getSharedPreferences(Context context) { + return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + } + + static long getDiscoverableEndTimestamp(Context context) { + return getSharedPreferences(context).getLong( + KEY_DISCOVERABLE_END_TIMESTAMP, 0); + } + + static boolean shouldShowDialogInForeground(Context context, + String deviceAddress) { + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context); + if (manager == null) { + return false; + } + + // If Bluetooth Settings is visible + if (manager.isForegroundActivity()) { + return true; + } + + long currentTimeMillis = System.currentTimeMillis(); + SharedPreferences sharedPreferences = getSharedPreferences(context); + + // If the device was in discoverABLE mode recently + long lastDiscoverableEndTime = sharedPreferences.getLong( + KEY_DISCOVERABLE_END_TIMESTAMP, 0); + if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) + > currentTimeMillis) { + return true; + } + + // If the device was discoverING recently + LocalBluetoothAdapter adapter = manager.getBluetoothAdapter(); + if (adapter != null && adapter.isDiscovering()) { + return true; + } else if ((sharedPreferences.getLong(KEY_DISCOVERING_TIMESTAMP, 0) + + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) { + return true; + } + + // If the device was picked in the device picker recently + if (deviceAddress != null) { + String lastSelectedDevice = sharedPreferences.getString( + KEY_LAST_SELECTED_DEVICE, null); + + if (deviceAddress.equals(lastSelectedDevice)) { + long lastDeviceSelectedTime = sharedPreferences.getLong( + KEY_LAST_SELECTED_DEVICE_TIME, 0); + if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) + > currentTimeMillis) { + return true; + } + } + } + return false; + } + + static void persistSelectedDeviceInPicker(Context context, String deviceAddress) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putString(KEY_LAST_SELECTED_DEVICE, + deviceAddress); + editor.putLong(KEY_LAST_SELECTED_DEVICE_TIME, + System.currentTimeMillis()); + editor.apply(); + } + + static void persistDiscoverableEndTimestamp(Context context, long endTimestamp) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putLong(KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp); + editor.apply(); + } + + static void persistDiscoveringTimestamp(final Context context) { + // Load the shared preferences and edit it on a background + // thread (but serialized!). + QueuedWork.singleThreadExecutor().submit(new Runnable() { + public void run() { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putLong( + KEY_DISCOVERING_TIMESTAMP, + System.currentTimeMillis()); + editor.apply(); + } + }); + } + + static boolean hasDockAutoConnectSetting(Context context, String addr) { + return getSharedPreferences(context).contains(KEY_DOCK_AUTO_CONNECT + addr); + } + + static boolean getDockAutoConnectSetting(Context context, String addr) { + return getSharedPreferences(context).getBoolean(KEY_DOCK_AUTO_CONNECT + addr, + false); + } + + static void saveDockAutoConnectSetting(Context context, String addr, boolean autoConnect) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putBoolean(KEY_DOCK_AUTO_CONNECT + addr, autoConnect); + editor.apply(); + } + + static void removeDockAutoConnectSetting(Context context, String addr) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.remove(KEY_DOCK_AUTO_CONNECT + addr); + editor.apply(); + } +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfile.java b/src/com/android/settings/bluetooth/LocalBluetoothProfile.java new file mode 100644 index 000000000..936231a1c --- /dev/null +++ b/src/com/android/settings/bluetooth/LocalBluetoothProfile.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; + +/** + * LocalBluetoothProfile is an interface defining the basic + * functionality related to a Bluetooth profile. + */ +interface LocalBluetoothProfile { + + /** + * Returns true if the user can initiate a connection, false otherwise. + */ + boolean isConnectable(); + + /** + * Returns true if the user can enable auto connection for this profile. + */ + boolean isAutoConnectable(); + + boolean connect(BluetoothDevice device); + + boolean disconnect(BluetoothDevice device); + + int getConnectionStatus(BluetoothDevice device); + + boolean isPreferred(BluetoothDevice device); + + int getPreferred(BluetoothDevice device); + + void setPreferred(BluetoothDevice device, boolean preferred); + + boolean isProfileReady(); + + /** Display order for device profile settings. */ + int getOrdinal(); + + /** + * Returns the string resource ID for the localized name for this profile. + */ + int getNameResource(); + + /** + * Returns the string resource ID for the disconnect confirmation text + * for this profile. + */ + int getDisconnectResource(); + + /** + * Returns the string resource ID for the summary text for this profile + * for the specified device, e.g. "Use for media audio" or + * "Connected to media audio". + * @param device the device to query for profile connection status + * @return a string resource ID for the profile summary text + */ + int getSummaryResourceForDevice(BluetoothDevice device); + + int getDrawableResource(BluetoothClass btClass); +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java index de15dcc50..0bb6f115b 100644 --- a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java +++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,67 +16,37 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothInputDevice; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; -import android.os.Handler; +import android.content.Context; +import android.content.Intent; import android.os.ParcelUuid; import android.util.Log; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; import java.util.Map; /** - * LocalBluetoothProfileManager is an abstract class defining the basic - * functionality related to a profile. + * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile + * objects for the available Bluetooth profiles. */ -abstract class LocalBluetoothProfileManager { +final class LocalBluetoothProfileManager { private static final String TAG = "LocalBluetoothProfileManager"; - /* package */ static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.HSP, - BluetoothUuid.Handsfree, - }; - - /* package */ static final ParcelUuid[] A2DP_SINK_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.AudioSink, - BluetoothUuid.AdvAudioDist, - }; - - /* package */ static final ParcelUuid[] A2DP_SRC_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.AudioSource - }; - - /* package */ static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.ObexObjectPush - }; - - /* package */ static final ParcelUuid[] HID_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.Hid - }; - - /* package */ static final ParcelUuid[] PANU_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.PANU - }; - - /* package */ static final ParcelUuid[] NAP_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.NAP - }; + /** Singleton instance. */ + private static LocalBluetoothProfileManager sInstance; /** * An interface for notifying BluetoothHeadset IPC clients when they have * been connected to the BluetoothHeadset service. + * Only used by {@link DockService}. */ public interface ServiceListener { /** @@ -85,7 +55,7 @@ abstract class LocalBluetoothProfileManager { * this callback before making IPC calls on the BluetoothHeadset * service. */ - public void onServiceConnected(); + void onServiceConnected(); /** * Called to notify the client that this proxy object has been @@ -94,723 +64,246 @@ abstract class LocalBluetoothProfileManager { * This callback will currently only occur if the application hosting * the BluetoothHeadset service, but may be called more often in future. */ - public void onServiceDisconnected(); + void onServiceDisconnected(); } - // TODO: close profiles when we're shutting down - private static final Map<Profile, LocalBluetoothProfileManager> sProfileMap = - new HashMap<Profile, LocalBluetoothProfileManager>(); - - protected final LocalBluetoothManager mLocalManager; + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final BluetoothEventManager mEventManager; - public static void init(LocalBluetoothManager localManager) { - synchronized (sProfileMap) { - if (sProfileMap.size() == 0) { - LocalBluetoothProfileManager profileManager; - - profileManager = new A2dpProfileManager(localManager); - sProfileMap.put(Profile.A2DP, profileManager); - - profileManager = new HeadsetProfileManager(localManager); - sProfileMap.put(Profile.HEADSET, profileManager); - - profileManager = new OppProfileManager(localManager); - sProfileMap.put(Profile.OPP, profileManager); - - profileManager = new HidProfileManager(localManager); - sProfileMap.put(Profile.HID, profileManager); - - profileManager = new PanProfileManager(localManager); - sProfileMap.put(Profile.PAN, profileManager); - } - } - } - - // TODO(): Combine the init and updateLocalProfiles codes. - // init can get called from various paths, it makes no sense to add and then delete. - public static void updateLocalProfiles(LocalBluetoothManager localManager, ParcelUuid[] uuids) { - if (!BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) && - !BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { - sProfileMap.remove(Profile.HEADSET); - } - - if (!BluetoothUuid.containsAnyUuid(uuids, A2DP_SRC_PROFILE_UUIDS)) { - sProfileMap.remove(Profile.A2DP); - } - - if (!BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) { - sProfileMap.remove(Profile.OPP); - } - - // There is no local SDP record for HID and Settings app doesn't control PBAP - } - - private static final LinkedList<ServiceListener> mServiceListeners = - new LinkedList<ServiceListener>(); - - public static void addServiceListener(ServiceListener l) { - mServiceListeners.add(l); - } - - public static void removeServiceListener(ServiceListener l) { - mServiceListeners.remove(l); - } - - public static boolean isManagerReady() { - // Getting just the headset profile is fine for now. Will need to deal with A2DP - // and others if they aren't always in a ready state. - LocalBluetoothProfileManager profileManager = sProfileMap.get(Profile.HEADSET); - if (profileManager == null) { - return sProfileMap.size() > 0; - } - return profileManager.isProfileReady(); - } - - public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager, - Profile profile) { - // Note: This code assumes that "localManager" is same as the - // LocalBluetoothManager that was used to initialize the sProfileMap. - // If that every changes, we can't just keep one copy of sProfileMap. - synchronized (sProfileMap) { - LocalBluetoothProfileManager profileManager = sProfileMap.get(profile); - if (profileManager == null) { - Log.e(TAG, "profileManager can't be found for " + profile.toString()); - } - return profileManager; - } - } + private A2dpProfile mA2dpProfile; + private HeadsetProfile mHeadsetProfile; + private final HidProfile mHidProfile; + private OppProfile mOppProfile; + private final PanProfile mPanProfile; /** - * Temporary method to fill profiles based on a device's class. - * - * NOTE: This list happens to define the connection order. We should put this logic in a more - * well known place when this method is no longer temporary. - * @param uuids of the remote device - * @param localUuids UUIDs of the local device - * @param profiles The list of profiles to fill + * Mapping from profile name, e.g. "HEADSET" to profile object. */ - public static void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, - List<Profile> profiles) { - profiles.clear(); + private final Map<String, LocalBluetoothProfile> + mProfileNameMap = new HashMap<String, LocalBluetoothProfile>(); - if (uuids == null) { - return; - } + LocalBluetoothProfileManager(Context context, + LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + BluetoothEventManager eventManager) { + mContext = context; - if (sProfileMap.containsKey(Profile.HEADSET)) { - if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && - BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || - (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && - BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { - profiles.add(Profile.HEADSET); - } - } + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mEventManager = eventManager; + // pass this reference to adapter and event manager (circular dependency) + mLocalAdapter.setProfileManager(this); + mEventManager.setProfileManager(this); + ParcelUuid[] uuids = adapter.getUuids(); - if (BluetoothUuid.containsAnyUuid(uuids, A2DP_SINK_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.A2DP)) { - profiles.add(Profile.A2DP); + // uuids may be null if Bluetooth is turned off + if (uuids != null) { + updateLocalProfiles(uuids); } - if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.OPP)) { - profiles.add(Profile.OPP); - } + // Always add HID and PAN profiles + mHidProfile = new HidProfile(context, mLocalAdapter); + addProfile(mHidProfile, HidProfile.NAME, + BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); - if (BluetoothUuid.containsAnyUuid(uuids, HID_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.HID)) { - profiles.add(Profile.HID); - } - - if (BluetoothUuid.containsAnyUuid(uuids, NAP_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.PAN)) { - profiles.add(Profile.PAN); - } - } - - protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) { - mLocalManager = localManager; - } - - public abstract List<BluetoothDevice> getConnectedDevices(); - - public abstract boolean connect(BluetoothDevice device); - - public abstract boolean disconnect(BluetoothDevice device); - - public abstract int getConnectionStatus(BluetoothDevice device); - - public abstract int getSummary(BluetoothDevice device); - - public abstract int convertState(int a2dpState); - - public abstract boolean isPreferred(BluetoothDevice device); - - public abstract int getPreferred(BluetoothDevice device); - - public abstract void setPreferred(BluetoothDevice device, boolean preferred); - - public boolean isConnected(BluetoothDevice device) { - return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device)); - } - - public abstract boolean isProfileReady(); - - public abstract int getDrawableResource(); - - public static enum Profile { - HEADSET(R.string.bluetooth_profile_headset, true, true), - A2DP(R.string.bluetooth_profile_a2dp, true, true), - OPP(R.string.bluetooth_profile_opp, false, false), - HID(R.string.bluetooth_profile_hid, true, true), - PAN(R.string.bluetooth_profile_pan, true, false); - - public final int localizedString; - private final boolean mConnectable; - private final boolean mAutoConnectable; - - private Profile(int localizedString, boolean connectable, - boolean autoConnectable) { - this.localizedString = localizedString; - this.mConnectable = connectable; - this.mAutoConnectable = autoConnectable; - } - - public boolean isConnectable() { - return mConnectable; - } - - public boolean isAutoConnectable() { - return mAutoConnectable; - } + mPanProfile = new PanProfile(context); + addProfile(mPanProfile, PanProfile.NAME, BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); + Log.d(TAG, "LocalBluetoothProfileManager construction complete"); } /** - * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. + * Initialize or update the local profile objects. If a UUID was previously + * present but has been removed, we print a warning but don't remove the + * profile object as it might be referenced elsewhere, or the UUID might + * come back and we don't want multiple copies of the profile objects. + * @param uuids */ - private static class A2dpProfileManager extends LocalBluetoothProfileManager - implements BluetoothProfile.ServiceListener { - private BluetoothA2dp mService; - - // TODO(): The calls must wait for mService. Its not null just - // because it runs in the system server. - public A2dpProfileManager(LocalBluetoothManager localManager) { - super(localManager); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.A2DP); - - } - - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mService = (BluetoothA2dp) proxy; - } - - public void onServiceDisconnected(int profile) { - mService = null; - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - return mService.getDevicesMatchingConnectionStates( - new int[] {BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}); - } - - @Override - public boolean connect(BluetoothDevice device) { - List<BluetoothDevice> sinks = getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - // Downgrade priority as user is disconnecting the sink. - if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - return mService.disconnect(device); - } - - @Override - public int getConnectionStatus(BluetoothDevice device) { - return convertState(mService.getConnectionState(device)); - } - - @Override - public int getSummary(BluetoothDevice device) { - int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_a2dp_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); + void updateLocalProfiles(ParcelUuid[] uuids) { + // A2DP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { + if (mA2dpProfile == null) { + Log.d(TAG, "Adding local A2DP profile"); + mA2dpProfile = new A2dpProfile(mContext); + addProfile(mA2dpProfile, A2dpProfile.NAME, + BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); } - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; - } - - @Override - public int getPreferred(BluetoothDevice device) { - return mService.getPriority(device); - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (preferred) { - if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - } else { - mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } else if (mA2dpProfile != null) { + Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing."); + } + + // Headset / Handsfree + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) || + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { + if (mHeadsetProfile == null) { + Log.d(TAG, "Adding local HEADSET profile"); + mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mHeadsetProfile, HeadsetProfile.NAME, + BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); } + } else if (mHeadsetProfile != null) { + Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing."); } - @Override - public int convertState(int a2dpState) { - switch (a2dpState) { - case BluetoothProfile.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothProfile.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothProfile.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - case BluetoothProfile.STATE_DISCONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; - case BluetoothA2dp.STATE_PLAYING: - return SettingsBtStatus.CONNECTION_STATUS_ACTIVE; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; + // OPP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + if (mOppProfile == null) { + Log.d(TAG, "Adding local OPP profile"); + mOppProfile = new OppProfile(); + // Note: no event handler for OPP, only name map. + mProfileNameMap.put(OppProfile.NAME, mOppProfile); } + } else if (mOppProfile != null) { + Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing."); } - @Override - public boolean isProfileReady() { - return true; - } - - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_headphones_a2dp; - } + // There is no local SDP record for HID and Settings app doesn't control PBAP } - /** - * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service. - */ - private static class HeadsetProfileManager extends LocalBluetoothProfileManager - implements BluetoothProfile.ServiceListener { - private BluetoothHeadset mService; - private final Handler mUiHandler = new Handler(); - private boolean profileReady = false; - - // TODO(): The calls must get queued if mService becomes null. - // It can happen when phone app crashes for some reason. - // All callers should have service listeners. Dock Service is the only - // one right now. - public HeadsetProfileManager(LocalBluetoothManager localManager) { - super(localManager); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.HEADSET); - } - - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mService = (BluetoothHeadset) proxy; - profileReady = true; - // This could be called on a non-UI thread, funnel to UI thread. - mUiHandler.post(new Runnable() { - public void run() { - /* - * We just bound to the service, so refresh the UI of the - * headset device. - */ - List<BluetoothDevice> deviceList = mService.getConnectedDevices(); - if (deviceList.size() == 0) return; - - mLocalManager.getCachedDeviceManager() - .onProfileStateChanged(deviceList.get(0), Profile.HEADSET, - BluetoothProfile.STATE_CONNECTED); - } - }); - - if (mServiceListeners.size() > 0) { - Iterator<ServiceListener> it = mServiceListeners.iterator(); - while(it.hasNext()) { - it.next().onServiceConnected(); - } - } - } - - public void onServiceDisconnected(int profile) { - mService = null; - profileReady = false; - if (mServiceListeners.size() > 0) { - Iterator<ServiceListener> it = mServiceListeners.iterator(); - while(it.hasNext()) { - it.next().onServiceDisconnected(); - } - } - } + private final Collection<ServiceListener> mServiceListeners = + new ArrayList<ServiceListener>(); - @Override - public boolean isProfileReady() { - return profileReady; - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - if (mService != null) { - return mService.getConnectedDevices(); - } else { - return new ArrayList<BluetoothDevice>(); - } - } - - @Override - public boolean connect(BluetoothDevice device) { - List<BluetoothDevice> sinks = getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - List<BluetoothDevice> deviceList = getConnectedDevices(); - if (deviceList.size() != 0 && deviceList.get(0).equals(device)) { - // Downgrade priority as user is disconnecting the headset. - if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - return mService.disconnect(device); - } else { - return false; - } - } - - @Override - public int getConnectionStatus(BluetoothDevice device) { - List<BluetoothDevice> deviceList = getConnectedDevices(); - - return deviceList.size() > 0 && deviceList.get(0).equals(device) - ? convertState(mService.getConnectionState(device)) - : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - } - - @Override - public int getSummary(BluetoothDevice device) { - int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_headset_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); - } - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; - } - - @Override - public int getPreferred(BluetoothDevice device) { - return mService.getPriority(device); - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (preferred) { - if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - } else { - mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); - } - } + private void addProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addHandler(stateChangedAction, new StateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } - @Override - public int convertState(int headsetState) { - switch (headsetState) { - case BluetoothProfile.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothProfile.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothProfile.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; - } - } + LocalBluetoothProfile getProfileByName(String name) { + return mProfileNameMap.get(name); + } - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_headset_hfp; + // Called from LocalBluetoothAdapter when state changes to ON + void setBluetoothStateOn() { + ParcelUuid[] uuids = mLocalAdapter.getUuids(); + if (uuids != null) { + updateLocalProfiles(uuids); } + mEventManager.readPairedDevices(); } /** - * OppProfileManager + * Generic handler for connection state change events for the specified profile. */ - private static class OppProfileManager extends LocalBluetoothProfileManager { - - public OppProfileManager(LocalBluetoothManager localManager) { - super(localManager); - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - return null; - } - - @Override - public boolean connect(BluetoothDevice device) { - return false; - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return false; - } + private class StateChangedHandler implements BluetoothEventManager.Handler { + private final LocalBluetoothProfile mProfile; - @Override - public int getConnectionStatus(BluetoothDevice device) { - return -1; + StateChangedHandler(LocalBluetoothProfile profile) { + mProfile = profile; } - @Override - public int getSummary(BluetoothDevice device) { - int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_opp_profile_summary_connected; - } else { - return R.string.bluetooth_opp_profile_summary_not_connected; + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "StateChangedHandler found new device: " + device); + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, + LocalBluetoothProfileManager.this, device); } - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return false; - } - - @Override - public int getPreferred(BluetoothDevice device) { - return -1; - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - } - - @Override - public boolean isProfileReady() { - return true; - } - - @Override - public int convertState(int oppState) { - switch (oppState) { - case 0: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case 1: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case 2: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); + if (newState == BluetoothProfile.STATE_DISCONNECTED && + oldState == BluetoothProfile.STATE_CONNECTING) { + Log.i(TAG, "Failed to connect " + mProfile + " device"); } - } - @Override - public int getDrawableResource() { - return 0; // no icon for OPP + cachedDevice.onProfileStateChanged(mProfile, newState); + cachedDevice.refresh(); } } - private static class HidProfileManager extends LocalBluetoothProfileManager { - private final BluetoothInputDevice mService; - - public HidProfileManager(LocalBluetoothManager localManager) { - super(localManager); - mService = new BluetoothInputDevice(localManager.getContext()); - } - - @Override - public boolean connect(BluetoothDevice device) { - return mService.connectInputDevice(device); - } - - @Override - public int convertState(int hidState) { - switch (hidState) { - case BluetoothInputDevice.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothInputDevice.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothInputDevice.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - case BluetoothInputDevice.STATE_DISCONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; - } - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return mService.disconnectInputDevice(device); - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - return mService.getConnectedInputDevices(); - } - - @Override - public int getConnectionStatus(BluetoothDevice device) { - return convertState(mService.getInputDeviceState(device)); - } - - @Override - public int getPreferred(BluetoothDevice device) { - return mService.getInputDevicePriority(device); - } - - @Override - public int getSummary(BluetoothDevice device) { - final int connectionStatus = getConnectionStatus(device); + // called from DockService + void addServiceListener(ServiceListener l) { + mServiceListeners.add(l); + } - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_hid_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); - } - } + // called from DockService + void removeServiceListener(ServiceListener l) { + mServiceListeners.remove(l); + } - @Override - public boolean isPreferred(BluetoothDevice device) { - return mService.getInputDevicePriority(device) > BluetoothInputDevice.PRIORITY_OFF; + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceConnectedListeners() { + for (ServiceListener l : mServiceListeners) { + l.onServiceConnected(); } + } - @Override - public boolean isProfileReady() { - return true; + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceDisconnectedListeners() { + for (ServiceListener listener : mServiceListeners) { + listener.onServiceDisconnected(); } + } - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (preferred) { - if (mService.getInputDevicePriority(device) < BluetoothInputDevice.PRIORITY_ON) { - mService.setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_ON); - } - } else { - mService.setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_OFF); - } + // This is called by DockService, so check Headset and A2DP. + public synchronized boolean isManagerReady() { + // Getting just the headset profile is fine for now. Will need to deal with A2DP + // and others if they aren't always in a ready state. + LocalBluetoothProfile profile = mHeadsetProfile; + if (profile != null) { + return profile.isProfileReady(); } - - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_keyboard_hid; + profile = mA2dpProfile; + if (profile != null) { + return profile.isProfileReady(); } + return false; } - private static class PanProfileManager extends LocalBluetoothProfileManager { - private final BluetoothPan mService; - - public PanProfileManager(LocalBluetoothManager localManager) { - super(localManager); - mService = new BluetoothPan(localManager.getContext()); - } + A2dpProfile getA2dpProfile() { + return mA2dpProfile; + } - @Override - public boolean connect(BluetoothDevice device) { - List<BluetoothDevice> sinks = getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } + HeadsetProfile getHeadsetProfile() { + return mHeadsetProfile; + } - @Override - public int convertState(int panState) { - switch (panState) { - case BluetoothPan.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothPan.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothPan.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - case BluetoothPan.STATE_DISCONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; - } - } + /** + * Fill in a list of LocalBluetoothProfile objects that are supported by + * the local device and the remote device. + * + * @param uuids of the remote device + * @param localUuids UUIDs of the local device + * @param profiles The list of profiles to fill + */ + synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, + Collection<LocalBluetoothProfile> profiles) { + profiles.clear(); - @Override - public boolean disconnect(BluetoothDevice device) { - return mService.disconnect(device); + if (uuids == null) { + return; } - @Override - public int getSummary(BluetoothDevice device) { - final int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_pan_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); + if (mHeadsetProfile != null) { + if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || + (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { + profiles.add(mHeadsetProfile); } } - @Override - public boolean isProfileReady() { - return true; + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && + mA2dpProfile != null) { + profiles.add(mA2dpProfile); } - @Override - public List<BluetoothDevice> getConnectedDevices() { - return mService.getConnectedDevices(); + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && + mOppProfile != null) { + profiles.add(mOppProfile); } - @Override - public int getConnectionStatus(BluetoothDevice device) { - return convertState(mService.getPanDeviceState(device)); - } - - @Override - public int getPreferred(BluetoothDevice device) { - return -1; - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return true; - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PAN - return; + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) && + mHidProfile != null) { + profiles.add(mHidProfile); } - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_network_pan; + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) && + mPanProfile != null) { + profiles.add(mPanProfile); } } } diff --git a/src/com/android/settings/bluetooth/OppProfile.java b/src/com/android/settings/bluetooth/OppProfile.java new file mode 100644 index 000000000..3f7df3853 --- /dev/null +++ b/src/com/android/settings/bluetooth/OppProfile.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import com.android.settings.R; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; + +/** + * OppProfile handles Bluetooth OPP. + */ +final class OppProfile implements LocalBluetoothProfile { + + static final String NAME = "OPP"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 2; + + public boolean isConnectable() { + return false; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + return false; + } + + public boolean disconnect(BluetoothDevice device) { + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + } + + public boolean isProfileReady() { + return true; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_opp; + } + + public int getDisconnectResource() { + return 0; // user must use notification to disconnect OPP transfer. + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return 0; // OPP profile not displayed in UI + } + + public int getDrawableResource(BluetoothClass btClass) { + return 0; // no icon for OPP + } +} diff --git a/src/com/android/settings/bluetooth/PanProfile.java b/src/com/android/settings/bluetooth/PanProfile.java new file mode 100644 index 000000000..3f456e45b --- /dev/null +++ b/src/com/android/settings/bluetooth/PanProfile.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settings.R; + +import java.util.List; + +/** + * PanProfile handles Bluetooth PAN profile. + */ +final class PanProfile implements LocalBluetoothProfile { + private BluetoothPan mService; + + static final String NAME = "PAN"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 4; + + // These callbacks run on the main thread. + private final class PanServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothPan) proxy; + } + + public void onServiceDisconnected(int profile) { + mService = null; + } + } + + PanProfile(Context context) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter.getProfileProxy(context, new PanServiceListener(), + BluetoothProfile.PAN); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + return true; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PAN + } + + public boolean isProfileReady() { + return true; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_pan; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_pan_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_pan_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_pan_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_network_pan; + } +} diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java index 97ec01704..07a7316cb 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java @@ -27,7 +27,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; @@ -43,7 +42,7 @@ public class RequestPermissionActivity extends Activity implements private static final String TAG = "RequestPermissionActivity"; - private static final int MAX_DISCOVERABLE_TIMEOUT = 300; + private static final int MAX_DISCOVERABLE_TIMEOUT = 3600; // 1 hr // Non-error return code: BT is starting or has started successfully. Used // by this Activity and RequestPermissionHelperActivity @@ -51,7 +50,7 @@ public class RequestPermissionActivity extends Activity implements private static final int REQUEST_CODE_START_BT = 1; - private LocalBluetoothManager mLocalManager; + private LocalBluetoothAdapter mLocalAdapter; private int mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT; @@ -66,18 +65,19 @@ public class RequestPermissionActivity extends Activity implements // True if requesting BT to be turned on // False if requesting BT to be turned on + discoverable mode - private boolean mEnableOnly = false; + private boolean mEnableOnly; - private boolean mUserConfirmed = false; + private boolean mUserConfirmed; - private AlertDialog mDialog = null; + private AlertDialog mDialog; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent == null) + if (intent == null) { return; + } if (mNeededToEnableBluetooth && BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR); @@ -94,12 +94,13 @@ public class RequestPermissionActivity extends Activity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Note: initializes mLocalAdapter and returns true on error if (parseIntent()) { finish(); return; } - int btState = mLocalManager.getBluetoothState(); + int btState = mLocalAdapter.getState(); switch (btState) { case BluetoothAdapter.STATE_OFF: @@ -120,28 +121,29 @@ public class RequestPermissionActivity extends Activity implements */ registerReceiver(mReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); - Intent i = new Intent(); - i.setClass(this, RequestPermissionHelperActivity.class); + Intent intent = new Intent(); + intent.setClass(this, RequestPermissionHelperActivity.class); if (mEnableOnly) { - i.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON); + intent.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON); } else { - i.setAction(RequestPermissionHelperActivity. + intent.setAction(RequestPermissionHelperActivity. ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE); - i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout); + intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout); } - startActivityForResult(i, REQUEST_CODE_START_BT); + startActivityForResult(intent, REQUEST_CODE_START_BT); mNeededToEnableBluetooth = true; break; case BluetoothAdapter.STATE_ON: if (mEnableOnly) { // Nothing to do. Already enabled. proceedAndFinish(); - return; } else { // Ask the user about enabling discovery mode createDialog(); - break; } + break; + default: + Log.e(TAG, "Unknown adapter state: " + btState); } } @@ -157,7 +159,14 @@ public class RequestPermissionActivity extends Activity implements builder.setCancelable(false); } else { // Ask the user whether to turn on discovery mode or not - builder.setMessage(getString(R.string.bluetooth_ask_enablement_and_discovery, mTimeout)); + // For lasting discoverable mode there is a different message + if (mTimeout == BluetoothDiscoverableEnabler.DISCOVERABLE_TIMEOUT_NEVER) { + builder.setMessage( + getString(R.string.bluetooth_ask_lasting_discovery)); + } else { + builder.setMessage( + getString(R.string.bluetooth_ask_discovery, mTimeout)); + } builder.setPositiveButton(getString(R.string.yes), this); builder.setNegativeButton(getString(R.string.no), this); } @@ -169,8 +178,8 @@ public class RequestPermissionActivity extends Activity implements @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != REQUEST_CODE_START_BT) { - Log.e(TAG, "Unexpected onActivityResult " + requestCode + " " + resultCode); - setResult(Activity.RESULT_CANCELED); + Log.e(TAG, "Unexpected onActivityResult " + requestCode + ' ' + resultCode); + setResult(RESULT_CANCELED); finish(); return; } @@ -184,7 +193,7 @@ public class RequestPermissionActivity extends Activity implements // BT and discoverable mode. mUserConfirmed = true; - if (mLocalManager.getBluetoothState() == BluetoothAdapter.STATE_ON) { + if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) { proceedAndFinish(); } else { // If BT is not up yet, show "Turning on Bluetooth..." @@ -199,7 +208,7 @@ public class RequestPermissionActivity extends Activity implements break; case DialogInterface.BUTTON_NEGATIVE: - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); finish(); break; } @@ -210,18 +219,19 @@ public class RequestPermissionActivity extends Activity implements if (mEnableOnly) { // BT enabled. Done - returnCode = Activity.RESULT_OK; - } else if (mLocalManager.getBluetoothAdapter().setScanMode( + returnCode = RESULT_OK; + } else if (mLocalAdapter.setScanMode( BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout)) { // If already in discoverable mode, this will extend the timeout. - persistDiscoverableEndTimestamp(System.currentTimeMillis() + mTimeout * 1000); + LocalBluetoothPreferences.persistDiscoverableEndTimestamp( + this, System.currentTimeMillis() + (long) mTimeout * 1000); returnCode = mTimeout; // Activity.RESULT_FIRST_USER should be 1 - if (returnCode < Activity.RESULT_FIRST_USER) { - returnCode = Activity.RESULT_FIRST_USER; + if (returnCode < RESULT_FIRST_USER) { + returnCode = RESULT_FIRST_USER; } } else { - returnCode = Activity.RESULT_CANCELED; + returnCode = RESULT_CANCELED; } if (mDialog != null) { @@ -232,6 +242,10 @@ public class RequestPermissionActivity extends Activity implements finish(); } + /** + * Parse the received Intent and initialize mLocalBluetoothAdapter. + * @return true if an error occurred; false otherwise + */ private boolean parseIntent() { Intent intent = getIntent(); if (intent != null && intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_ENABLE)) { @@ -241,27 +255,26 @@ public class RequestPermissionActivity extends Activity implements mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT); - Log.e(TAG, "Timeout = " + mTimeout); + Log.d(TAG, "Setting Bluetooth Discoverable Timeout = " + mTimeout); - if (mTimeout > MAX_DISCOVERABLE_TIMEOUT) { - mTimeout = MAX_DISCOVERABLE_TIMEOUT; - } else if (mTimeout <= 0) { + if (mTimeout < 0 || mTimeout > MAX_DISCOVERABLE_TIMEOUT) { mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT; } } else { Log.e(TAG, "Error: this activity may be started only with intent " + BluetoothAdapter.ACTION_REQUEST_ENABLE + " or " + BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); return true; } - mLocalManager = LocalBluetoothManager.getInstance(this); - if (mLocalManager == null) { - Log.e(TAG, "Error: there's a problem starting bluetooth"); - setResult(Activity.RESULT_CANCELED); + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Error: there's a problem starting Bluetooth"); + setResult(RESULT_CANCELED); return true; } + mLocalAdapter = manager.getBluetoothAdapter(); return false; } @@ -269,20 +282,14 @@ public class RequestPermissionActivity extends Activity implements @Override protected void onDestroy() { super.onDestroy(); - if (mNeededToEnableBluetooth) unregisterReceiver(mReceiver); - } - - private void persistDiscoverableEndTimestamp(long endTimestamp) { - SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit(); - editor.putLong( - BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, - endTimestamp); - editor.apply(); + if (mNeededToEnableBluetooth) { + unregisterReceiver(mReceiver); + } } @Override public void onBackPressed() { - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); super.onBackPressed(); } } diff --git a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java index c8698687f..9b5946b59 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java @@ -43,7 +43,7 @@ public class RequestPermissionHelperActivity extends AlertActivity implements public static final String ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE = "com.android.settings.bluetooth.ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE"; - private LocalBluetoothManager mLocalManager; + private LocalBluetoothAdapter mLocalAdapter; private int mTimeout; @@ -55,6 +55,7 @@ public class RequestPermissionHelperActivity extends AlertActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Note: initializes mLocalAdapter and returns true on error if (parseIntent()) { finish(); return; @@ -75,7 +76,11 @@ public class RequestPermissionHelperActivity extends AlertActivity implements if (mEnableOnly) { tv.setText(getString(R.string.bluetooth_ask_enablement)); } else { - tv.setText(getString(R.string.bluetooth_ask_enablement_and_discovery, mTimeout)); + if (mTimeout == BluetoothDiscoverableEnabler.DISCOVERABLE_TIMEOUT_NEVER) { + tv.setText(getString(R.string.bluetooth_ask_enablement_and_lasting_discovery)); + } else { + tv.setText(getString(R.string.bluetooth_ask_enablement_and_discovery, mTimeout)); + } } p.mPositiveButtonText = getString(R.string.yes); @@ -88,32 +93,33 @@ public class RequestPermissionHelperActivity extends AlertActivity implements public void onClick(DialogInterface dialog, int which) { int returnCode; + // FIXME: fix this ugly switch logic! switch (which) { - case DialogInterface.BUTTON_POSITIVE: + case BUTTON_POSITIVE: int btState = 0; try { // TODO There's a better way. int retryCount = 30; do { - btState = mLocalManager.getBluetoothState(); + btState = mLocalAdapter.getBluetoothState(); Thread.sleep(100); } while (btState == BluetoothAdapter.STATE_TURNING_OFF && --retryCount > 0); - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { // don't care } if (btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON - || mLocalManager.getBluetoothAdapter().enable()) { + || mLocalAdapter.enable()) { returnCode = RequestPermissionActivity.RESULT_BT_STARTING_OR_STARTED; } else { - returnCode = Activity.RESULT_CANCELED; + returnCode = RESULT_CANCELED; } break; - case DialogInterface.BUTTON_NEGATIVE: - returnCode = Activity.RESULT_CANCELED; + case BUTTON_NEGATIVE: + returnCode = RESULT_CANCELED; break; default: return; @@ -121,6 +127,10 @@ public class RequestPermissionHelperActivity extends AlertActivity implements setResult(returnCode); } + /** + * Parse the received Intent and initialize mLocalBluetoothAdapter. + * @return true if an error occurred; false otherwise + */ private boolean parseIntent() { Intent intent = getIntent(); if (intent != null && intent.getAction().equals(ACTION_INTERNAL_REQUEST_BT_ON)) { @@ -132,23 +142,24 @@ public class RequestPermissionHelperActivity extends AlertActivity implements mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT); } else { - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); return true; } - mLocalManager = LocalBluetoothManager.getInstance(this); - if (mLocalManager == null) { - Log.e(TAG, "Error: there's a problem starting bluetooth"); - setResult(Activity.RESULT_CANCELED); + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Error: there's a problem starting Bluetooth"); + setResult(RESULT_CANCELED); return true; } + mLocalAdapter = manager.getBluetoothAdapter(); return false; } @Override public void onBackPressed() { - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); super.onBackPressed(); } } diff --git a/src/com/android/settings/bluetooth/SettingsBtStatus.java b/src/com/android/settings/bluetooth/SettingsBtStatus.java deleted file mode 100644 index 2407b533d..000000000 --- a/src/com/android/settings/bluetooth/SettingsBtStatus.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.bluetooth; - -import android.bluetooth.BluetoothDevice; - -import com.android.settings.R; - -/** - * SettingsBtStatus is a helper class that contains constants for various status - * codes. - */ -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 #isConnectionStatusConnected} 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; - } - - public static final int getPairingStatusSummary(int bondState) { - switch (bondState) { - case BluetoothDevice.BOND_BONDED: - return R.string.bluetooth_paired; - case BluetoothDevice.BOND_BONDING: - return R.string.bluetooth_pairing; - case BluetoothDevice.BOND_NONE: - return R.string.bluetooth_not_connected; - default: - return 0; - } - } -} diff --git a/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java b/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java new file mode 100644 index 000000000..bae6e5682 --- /dev/null +++ b/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.text.InputFilter; +import android.text.Spanned; + +/** + * This filter will constrain edits so that the text length is not + * greater than the specified number of bytes using UTF-8 encoding. + * <p>The JNI method used by {@link android.server.BluetoothService} + * to convert UTF-16 to UTF-8 doesn't support surrogate pairs, + * therefore code points outside of the basic multilingual plane + * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters, + * rather than a single 4-byte UTF-8 encoding. Dalvik implements this + * conversion in {@code convertUtf16ToUtf8()} in + * {@code dalvik/vm/UtfString.c}. + * <p>This JNI method is unlikely to change in the future due to + * backwards compatibility requirements. It's also unclear whether + * the installed base of Bluetooth devices would correctly handle the + * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6. + * However, this filter will still work in scenarios where surrogate + * pairs are encoded as 4 bytes, with the caveat that the maximum + * length will be constrained more conservatively than necessary. + */ +class Utf8ByteLengthFilter implements InputFilter { + private final int mMaxBytes; + + Utf8ByteLengthFilter(int maxBytes) { + mMaxBytes = maxBytes; + } + + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + int srcByteCount = 0; + // count UTF-8 bytes in source substring + for (int i = start; i < end; i++) { + char c = source.charAt(i); + srcByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3); + } + int destLen = dest.length(); + int destByteCount = 0; + // count UTF-8 bytes in destination excluding replaced section + for (int i = 0; i < destLen; i++) { + if (i < dstart || i >= dend) { + char c = dest.charAt(i); + destByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3); + } + } + int keepBytes = mMaxBytes - destByteCount; + if (keepBytes <= 0) { + return ""; + } else if (keepBytes >= srcByteCount) { + return null; // use original dest string + } else { + // find end position of largest sequence that fits in keepBytes + for (int i = start; i < end; i++) { + char c = source.charAt(i); + keepBytes -= (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3); + if (keepBytes < 0) { + return source.subSequence(start, i); + } + } + // If the entire substring fits, we should have returned null + // above, so this line should not be reached. If for some + // reason it is, return null to use the original dest string. + return null; + } + } +} diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java new file mode 100644 index 000000000..7d38e1700 --- /dev/null +++ b/src/com/android/settings/bluetooth/Utils.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.app.AlertDialog; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.DialogInterface; +import android.widget.Toast; + +import com.android.settings.R; + +/** + * Utils is a helper class that contains constants for various + * Android resource IDs, debug logging flags, and static methods + * for creating dialogs. + */ +final class Utils { + static final boolean V = false; // verbose logging + static final boolean D = true; // regular logging + + private Utils() { + } + + public static int getConnectionStateSummary(int connectionState) { + switch (connectionState) { + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_connected; + case BluetoothProfile.STATE_CONNECTING: + return R.string.bluetooth_connecting; + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_disconnected; + case BluetoothProfile.STATE_DISCONNECTING: + return R.string.bluetooth_disconnecting; + default: + return 0; + } + } + + // Create (or recycle existing) and show disconnect dialog. + static AlertDialog showDisconnectDialog(Context context, + AlertDialog dialog, + DialogInterface.OnClickListener disconnectListener, + CharSequence title, CharSequence message) { + if (dialog == null) { + dialog = new AlertDialog.Builder(context) + .setPositiveButton(android.R.string.ok, disconnectListener) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } else { + if (dialog.isShowing()) { + dialog.dismiss(); + } + // use disconnectListener for the correct profile(s) + CharSequence okText = context.getText(android.R.string.ok); + dialog.setButton(DialogInterface.BUTTON_POSITIVE, + okText, disconnectListener); + } + dialog.setTitle(title); + dialog.setMessage(message); + dialog.show(); + return dialog; + } + + // TODO: wire this up to show connection errors... + static void showConnectingError(Context context, String name) { + // if (!mIsConnectingErrorPossible) { + // return; + // } + // mIsConnectingErrorPossible = false; + + showError(context, name, R.string.bluetooth_connecting_error_message); + } + + static void showError(Context context, String name, int messageResId) { + String message = context.getString(messageResId, name); + new AlertDialog.Builder(context) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(R.string.bluetooth_error_title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, null) + .show(); + } +} diff --git a/src/com/android/settings/deviceinfo/Constants.java b/src/com/android/settings/deviceinfo/Constants.java new file mode 100644 index 000000000..9f494797d --- /dev/null +++ b/src/com/android/settings/deviceinfo/Constants.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.deviceinfo; + +import android.os.Environment; + +import java.util.ArrayList; +import java.util.List; + +/** + * Some of the constants used in this package + */ +class Constants { + static final int MEDIA_INDEX = 0; + static final int DOWNLOADS_INDEX = 1; + static final int PIC_VIDEO_INDEX = 2; + static final int MUSIC_INDEX = 3; + static final int MEDIA_APPS_DATA_INDEX = 4; + static final int MEDIA_MISC_INDEX = 5; + static final int NUM_MEDIA_DIRS_TRACKED = MEDIA_MISC_INDEX + 1; + + static class MediaDirectory { + final String[] mDirPaths; + final String mKey; + final String mPreferenceName; + MediaDirectory(String pref, String debugInfo, String... paths) { + mDirPaths = paths; + mKey = debugInfo; + mPreferenceName = pref; + } + } + static final ArrayList<MediaDirectory> mMediaDirs = new ArrayList<MediaDirectory>(); + static final List<String> ExclusionTargetsForMiscFiles = new ArrayList<String>(); + static { + mMediaDirs.add(MEDIA_INDEX, + new MediaDirectory(null, + "/sdcard", + Environment.getExternalStorageDirectory().getAbsolutePath())); + mMediaDirs.add(DOWNLOADS_INDEX, + new MediaDirectory("memory_internal_downloads", + "/sdcard/download", + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS).getAbsolutePath())); + mMediaDirs.add(PIC_VIDEO_INDEX, + new MediaDirectory("memory_internal_dcim", + "/sdcard/pic_video", + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DCIM).getAbsolutePath(), + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_MOVIES).getAbsolutePath(), + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES).getAbsolutePath())); + mMediaDirs.add(MUSIC_INDEX, + new MediaDirectory("memory_internal_music", + "/sdcard/audio", + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_MUSIC).getAbsolutePath(), + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_ALARMS).getAbsolutePath(), + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_NOTIFICATIONS).getAbsolutePath(), + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_RINGTONES).getAbsolutePath(), + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PODCASTS).getAbsolutePath())); + mMediaDirs.add(MEDIA_APPS_DATA_INDEX, + new MediaDirectory(null, + "/sdcard/Android", + Environment.getExternalStorageAndroidDataDir().getAbsolutePath())); + mMediaDirs.add(MEDIA_MISC_INDEX, + new MediaDirectory("memory_internal_media_misc", + "misc on /sdcard", + "not relevant")); + // prepare a lit of strings representing dirpaths that should be skipped while looking + // for 'other' files + for (int j = 0; j < Constants.NUM_MEDIA_DIRS_TRACKED - 1; j++) { + String[] dirs = Constants.mMediaDirs.get(j).mDirPaths; + int len = dirs.length; + if (len > 0) { + for (int k = 0; k < len; k++) { + ExclusionTargetsForMiscFiles.add(dirs[k]); + } + } + // also add /sdcard/Android + ExclusionTargetsForMiscFiles.add( + Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android"); + } + } +} diff --git a/src/com/android/settings/deviceinfo/FileItemInfoLayout.java b/src/com/android/settings/deviceinfo/FileItemInfoLayout.java new file mode 100644 index 000000000..990f7f2b8 --- /dev/null +++ b/src/com/android/settings/deviceinfo/FileItemInfoLayout.java @@ -0,0 +1,78 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.android.settings.deviceinfo; + +import com.android.settings.R; + +import android.content.Context; +import android.os.Environment; +import android.util.AttributeSet; +import android.view.ViewDebug; +import android.widget.CheckBox; +import android.widget.Checkable; +import android.widget.RelativeLayout; +import android.widget.TextView; + +/** + * Handles display of a single row entry on Settings --> Storage --> Misc Files screen + */ +public class FileItemInfoLayout extends RelativeLayout implements Checkable { + private TextView mFileNameView; + private TextView mFileSizeView; + private CheckBox mCheckbox; + private static final int mLengthExternalStorageDirPrefix = + Environment.getExternalStorageDirectory().getAbsolutePath().length() + 1; + + public FileItemInfoLayout(Context context) { + this(context, null); + } + + public FileItemInfoLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FileItemInfoLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void toggle() { + setChecked(!mCheckbox.isChecked()); + } + + /* (non-Javadoc) + * @see android.view.View#onFinishInflate() + */ + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mFileNameView = (TextView) findViewById(R.id.misc_filename); + mFileSizeView = (TextView) findViewById(R.id.misc_filesize); + mCheckbox = (CheckBox) findViewById(R.id.misc_checkbox); + } + + public void setFileName(String fileName) { + mFileNameView.setText(fileName.substring(mLengthExternalStorageDirPrefix)); + } + + public void setFileSize(String filesize) { + mFileSizeView.setText(filesize); + } + + @ViewDebug.ExportedProperty + public boolean isChecked() { + return mCheckbox.isChecked(); + } + + public CheckBox getCheckBox() { + return mCheckbox; + } + + /** + * <p>Changes the checked state of this text view.</p> + * + * @param checked true to check the text, false to uncheck it + */ + public void setChecked(boolean checked) { + mCheckbox.setChecked(checked); + } +}
\ No newline at end of file diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java index db1ff6554..7d2a55e2f 100644 --- a/src/com/android/settings/deviceinfo/Memory.java +++ b/src/com/android/settings/deviceinfo/Memory.java @@ -23,6 +23,7 @@ import com.android.settings.deviceinfo.MemoryMeasurement.MeasurementReceiver; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; +import android.app.DownloadManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -32,9 +33,7 @@ import android.content.DialogInterface.OnCancelListener; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; import android.graphics.drawable.shapes.RoundRectShape; -import android.hardware.UsbManager; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -56,8 +55,7 @@ import java.util.List; public class Memory extends SettingsPreferenceFragment implements OnCancelListener, MeasurementReceiver { - private static final String TAG = "Memory"; - static final boolean localLOGV = false; + private static final String TAG = "MemorySettings"; private static final String MEMORY_SD_SIZE = "memory_sd_size"; @@ -75,8 +73,6 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps"; - private static final String MEMORY_INTERNAL_MEDIA = "memory_internal_media"; - private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart"; private static final int DLG_CONFIRM_UNMOUNT = 1; @@ -94,13 +90,13 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen // Internal storage preferences private Preference mInternalSize; private Preference mInternalAvail; - private Preference mInternalMediaUsage; private Preference mInternalAppsUsage; + private final Preference[] mMediaPreferences = new Preference[Constants.NUM_MEDIA_DIRS_TRACKED]; private UsageBarPreference mInternalUsageChart; // Internal storage chart colors - private int mInternalMediaColor; private int mInternalAppsColor; + private int mInternalAvailColor; private int mInternalUsedColor; boolean mSdMountToggleAdded = true; @@ -134,9 +130,12 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen Bundle bundle = msg.getData(); final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE); final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE); - final long mediaUsed = bundle.getLong(MemoryMeasurement.MEDIA_USED); final long appsUsed = bundle.getLong(MemoryMeasurement.APPS_USED); - updateUiExact(totalSize, availSize, mediaUsed, appsUsed); + final long[] mediaSizes = new long[Constants.NUM_MEDIA_DIRS_TRACKED]; + for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) { + mediaSizes[i] = bundle.getLong(Constants.mMediaDirs.get(i).mKey); + } + updateUiExact(totalSize, availSize, appsUsed, mediaSizes); break; } case MSG_UI_UPDATE_EXTERNAL_APPROXIMATE: { @@ -175,31 +174,59 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen } mInternalSize = findPreference(MEMORY_INTERNAL_SIZE); - mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL); - mInternalMediaUsage = findPreference(MEMORY_INTERNAL_MEDIA); - mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS); - - mInternalMediaColor = mRes.getColor(R.color.memory_media_usage); mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage); mInternalUsedColor = android.graphics.Color.GRAY; - + mInternalAvailColor = mRes.getColor(R.color.memory_avail); + final int buttonSize = (int) mRes.getDimension(R.dimen.device_memory_usage_button_size); float[] radius = new float[] { 5f, 5f, 5f, 5f, 5f, 5f, 5f, 5f }; RoundRectShape shape1 = new RoundRectShape(radius, null, null); - ShapeDrawable mediaShape = new ShapeDrawable(shape1); - mediaShape.setIntrinsicWidth(32); - mediaShape.setIntrinsicHeight(32); - mediaShape.getPaint().setColor(mInternalMediaColor); - mInternalMediaUsage.setIcon(mediaShape); + // total available space + mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL); + ShapeDrawable availShape = new ShapeDrawable(shape1); + availShape.setIntrinsicWidth(buttonSize); + availShape.setIntrinsicHeight(buttonSize); + availShape.getPaint().setColor(mInternalAvailColor); + mInternalAvail.setIcon(availShape); + // used by apps + mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS); ShapeDrawable appsShape = new ShapeDrawable(shape1); - appsShape.setIntrinsicWidth(32); - appsShape.setIntrinsicHeight(32); + appsShape.setIntrinsicWidth(buttonSize); + appsShape.setIntrinsicHeight(buttonSize); appsShape.getPaint().setColor(mInternalAppsColor); mInternalAppsUsage.setIcon(appsShape); + // space used by individual major directories on /sdcard + for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) { + // nothing to be displayed for certain entries in Constants.mMediaDirs + if (Constants.mMediaDirs.get(i).mPreferenceName == null) { + continue; + } + mMediaPreferences[i] = findPreference(Constants.mMediaDirs.get(i).mPreferenceName); + ShapeDrawable shape = new ShapeDrawable(shape1); + shape.setIntrinsicWidth(buttonSize); + shape.setIntrinsicHeight(buttonSize); + int color = 0; + switch (i) { + case Constants.DOWNLOADS_INDEX: + color = mRes.getColor(R.color.memory_downloads); + break; + case Constants.PIC_VIDEO_INDEX: + color = mRes.getColor(R.color.memory_video); + break; + case Constants.MUSIC_INDEX: + color = mRes.getColor(R.color.memory_audio); + break; + case Constants.MEDIA_MISC_INDEX: + color = mRes.getColor(R.color.memory_misc); + break; + } + shape.getPaint().setColor(color); + mMediaPreferences[i].setIcon(shape); + } mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART); mMeasurement = MemoryMeasurement.getInstance(getActivity()); @@ -209,7 +236,7 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen @Override public void onResume() { super.onResume(); - + mMeasurement.setReceiver(this); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED); intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); intentFilter.addDataScheme("file"); @@ -282,6 +309,27 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen com.android.settings.Settings.ManageApplicationsActivity.class); startActivity(intent); return true; + } else if (preference == mMediaPreferences[Constants.DOWNLOADS_INDEX]) { + Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS) + .putExtra(DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true); + startActivity(intent); + return true; + } else if (preference == mMediaPreferences[Constants.MUSIC_INDEX]) { + Intent intent = new Intent("android.intent.action.GET_CONTENT"); + intent.setType("audio/mp3"); + startActivity(intent); + return true; + } else if (preference == mMediaPreferences[Constants.PIC_VIDEO_INDEX]) { + Intent intent = new Intent("android.intent.action.GET_CONTENT"); + intent.setType("image/jpeg"); + startActivity(intent); + return true; + } else if (preference == mMediaPreferences[Constants.MEDIA_MISC_INDEX]) { + Context context = getActivity().getApplicationContext(); + if (MemoryMeasurement.getInstance(context).isSizeOfMiscCategoryNonZero()) { + startActivity(new Intent(context, MiscFilesHandler.class)); + } + return true; } return false; @@ -375,7 +423,6 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen // Check if external media is in use. try { if (hasAppsAccessingStorage()) { - if (localLOGV) Log.i(TAG, "Do have storage users accessing media"); // Present dialog to user showDialogInner(DLG_CONFIRM_UNMOUNT); } else { @@ -400,19 +447,45 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen } } - private void updateUiExact(long totalSize, long availSize, long mediaSize, long appsSize) { + private void updateUiExact(long totalSize, long availSize, long appsSize, long[] mediaSizes) { // There are other things that can take up storage, but we didn't measure it. // add that unaccounted-for-usage to Apps Usage - final long appsPlusRemaining = totalSize - availSize - mediaSize; - + long appsPlusRemaining = totalSize - availSize - mediaSizes[Constants.DOWNLOADS_INDEX] - + mediaSizes[Constants.PIC_VIDEO_INDEX] - mediaSizes[Constants.MUSIC_INDEX] - + mediaSizes[Constants.MEDIA_MISC_INDEX]; mInternalSize.setSummary(formatSize(totalSize)); mInternalAvail.setSummary(formatSize(availSize)); - mInternalMediaUsage.setSummary(formatSize(mediaSize)); mInternalAppsUsage.setSummary(formatSize(appsPlusRemaining)); mInternalUsageChart.clear(); - mInternalUsageChart.addEntry(mediaSize / (float) totalSize, mInternalMediaColor); mInternalUsageChart.addEntry(appsPlusRemaining / (float) totalSize, mInternalAppsColor); + + for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) { + if (Constants.mMediaDirs.get(i).mPreferenceName == null) { + continue; + } + this.mMediaPreferences[i].setSummary(formatSize(mediaSizes[i])); + // don't add entry to color chart for media usage and for zero-sized dirs + if (i != Constants.MEDIA_INDEX && mediaSizes[i] > 0) { + int color = 0; + switch (i) { + case Constants.DOWNLOADS_INDEX: + color = mRes.getColor(R.color.memory_downloads); + break; + case Constants.PIC_VIDEO_INDEX: + color = mRes.getColor(R.color.memory_video); + break; + case Constants.MUSIC_INDEX: + color = mRes.getColor(R.color.memory_audio); + break; + case Constants.MEDIA_MISC_INDEX: + color = mRes.getColor(R.color.memory_misc); + break; + } + mInternalUsageChart.addEntry(mediaSizes[i] / (float) totalSize, color); + } + } + mInternalUsageChart.addEntry(availSize / (float) totalSize, mInternalAvailColor); mInternalUsageChart.commit(); } diff --git a/src/com/android/settings/deviceinfo/MemoryMeasurement.java b/src/com/android/settings/deviceinfo/MemoryMeasurement.java index 1aef20234..1b42bc10d 100644 --- a/src/com/android/settings/deviceinfo/MemoryMeasurement.java +++ b/src/com/android/settings/deviceinfo/MemoryMeasurement.java @@ -23,6 +23,7 @@ import android.util.Log; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -35,14 +36,16 @@ import java.util.List; * * Filesystem stats (using StatFs) * Directory measurements (using DefaultContainerService.measureDir) - * Applicaiton measurements (using PackageManager) + * Application measurements (using PackageManager) * * Then the calling application would just specify the type and an argument. * This class would keep track of it while the calling application would * decide on how to use it. */ public class MemoryMeasurement { - private static final String TAG = "MemoryMeasurement"; + private static final String TAG = "MemorySettings"; + private static final boolean LOCAL_LOGV = true; + static final boolean LOGV = LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE); public static final String TOTAL_SIZE = "total_size"; @@ -50,7 +53,7 @@ public class MemoryMeasurement { public static final String APPS_USED = "apps_used"; - public static final String MEDIA_USED = "media_used"; + private long[] mMediaSizes = new long[Constants.NUM_MEDIA_DIRS_TRACKED]; private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer"; @@ -66,13 +69,13 @@ public class MemoryMeasurement { // Internal memory fields private long mInternalTotalSize; private long mInternalAvailSize; - private long mInternalMediaSize; private long mInternalAppsSize; // External memory fields private long mExternalTotalSize; private long mExternalAvailSize; + List<FileInfo> mFileInfoForMisc; private MemoryMeasurement(Context context) { // Start the thread that will measure the disk usage. @@ -98,7 +101,9 @@ public class MemoryMeasurement { } public void setReceiver(MeasurementReceiver receiver) { - mReceiver = new WeakReference<MeasurementReceiver>(receiver); + if (mReceiver == null || mReceiver.get() == null) { + mReceiver = new WeakReference<MeasurementReceiver>(receiver); + } } public void measureExternal() { @@ -134,6 +139,9 @@ public class MemoryMeasurement { private void sendInternalExactUpdate() { MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null; if (receiver == null) { + if (LOGV) { + Log.i(TAG, "measurements dropped because receiver is null! wasted effort"); + } return; } @@ -141,7 +149,9 @@ public class MemoryMeasurement { bundle.putLong(TOTAL_SIZE, mInternalTotalSize); bundle.putLong(AVAIL_SIZE, mInternalAvailSize); bundle.putLong(APPS_USED, mInternalAppsSize); - bundle.putLong(MEDIA_USED, mInternalMediaSize); + for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) { + bundle.putLong(Constants.mMediaDirs.get(i).mKey, mMediaSizes[i]); + } receiver.updateExactInternal(bundle); } @@ -252,6 +262,7 @@ public class MemoryMeasurement { case MSG_CONNECTED: { IMediaContainerService imcs = (IMediaContainerService) msg.obj; measureExactInternalStorage(imcs); + break; } case MSG_DISCONNECT: { synchronized (mLock) { @@ -265,6 +276,7 @@ public class MemoryMeasurement { context.unbindService(mDefContainerConn); } } + break; } case MSG_COMPLETED: { mMeasured = true; @@ -356,24 +368,40 @@ public class MemoryMeasurement { if (context == null) { return; } - // We have to get installd to measure the package sizes. PackageManager pm = context.getPackageManager(); if (pm == null) { return; } + // measure sizes for all except "media_misc" - which is computed + for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED - 1; i++) { + mMediaSizes[i] = 0; + String[] dirs = Constants.mMediaDirs.get(i).mDirPaths; + int len = dirs.length; + if (len > 0) { + for (int k = 0; k < len; k++) { + long dirSize = getSize(imcs, dirs[k]); + mMediaSizes[i] += dirSize; + if (LOGV) { + Log.i(TAG, "size of " + dirs[k] + ": " + dirSize); + } + } + } + } - long mediaSize; - try { - mediaSize = imcs.calculateDirectorySize( - Environment.getExternalStorageDirectory().getAbsolutePath()); - } catch (Exception e) { - Log.i(TAG, "Could not read memory from default container service"); - return; + // compute the size of "misc" + mMediaSizes[Constants.MEDIA_MISC_INDEX] = mMediaSizes[Constants.MEDIA_INDEX]; + for (int i = 1; i < Constants.NUM_MEDIA_DIRS_TRACKED - 1; i++) { + mMediaSizes[Constants.MEDIA_MISC_INDEX] -= mMediaSizes[i]; + } + if (LOGV) { + Log.i(TAG, "media_misc size: " + mMediaSizes[Constants.MEDIA_MISC_INDEX]); } - mInternalMediaSize = mediaSize; + // compute the sizes of each of the files/directories under 'misc' category + measureSizesOfMisc(imcs); + // compute apps sizes final List<ApplicationInfo> apps = pm .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS); @@ -393,6 +421,43 @@ public class MemoryMeasurement { // Sending of the message back to the MeasurementReceiver is // completed in the PackageObserver } + private void measureSizesOfMisc(IMediaContainerService imcs) { + File top = Environment.getExternalStorageDirectory(); + mFileInfoForMisc = new ArrayList<FileInfo>(); + File[] files = top.listFiles(); + int len = files.length; + if (len == 0) { + return; + } + // get sizes of all top level nodes in /sdcard dir except the ones already computed... + long counter = 0; + for (int i = 0; i < len; i++) { + String path = files[i].getAbsolutePath(); + if (Constants.ExclusionTargetsForMiscFiles.contains(path)) { + continue; + } + if (files[i].isFile()) { + mFileInfoForMisc.add(new FileInfo(path, files[i].length(), counter++)); + } else if (files[i].isDirectory()) { + long dirSize = getSize(imcs, path); + mFileInfoForMisc.add(new FileInfo(path, dirSize, counter++)); + } else { + } + } + // sort the list of FileInfo objects collected above in descending order of their sizes + Collections.sort(mFileInfoForMisc); + } + + private long getSize(IMediaContainerService imcs, String dir) { + try { + long size = imcs.calculateDirectorySize(dir); + return size; + } catch (Exception e) { + Log.w(TAG, "Could not read memory from default container service for " + + dir, e); + return -1; + } + } public void measureApproximateExternalStorage() { File path = Environment.getExternalStorageDirectory(); @@ -412,4 +477,29 @@ public class MemoryMeasurement { public void invalidate() { mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE); } + + boolean isSizeOfMiscCategoryNonZero() { + return mFileInfoForMisc != null && mFileInfoForMisc.size() > 0; + } + + static class FileInfo implements Comparable<FileInfo> { + String mFileName; + long mSize; + long mId; + FileInfo(String fileName, long size, long id) { + mFileName = fileName; + mSize = size; + mId = id; + } + @Override + public int compareTo(FileInfo that) { + if (this == that || mSize == that.mSize) return 0; + else if (mSize < that.mSize) return 1; // for descending sort + else return -1; + } + @Override + public String toString() { + return mFileName + " : " + mSize + ", id:" + mId; + } + } } diff --git a/src/com/android/settings/deviceinfo/MiscFilesHandler.java b/src/com/android/settings/deviceinfo/MiscFilesHandler.java new file mode 100644 index 000000000..15951c959 --- /dev/null +++ b/src/com/android/settings/deviceinfo/MiscFilesHandler.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo; + +import com.android.settings.R; +import com.android.settings.deviceinfo.MemoryMeasurement.FileInfo; + +import android.app.ListActivity; +import android.content.Context; +import android.os.Bundle; +import android.text.format.Formatter; +import android.util.Log; +import android.util.SparseBooleanArray; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ListView; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * This class handles the selection and removal of Misc files. + */ +public class MiscFilesHandler extends ListActivity { + private static final String TAG = "MemorySettings"; + private String mNumSelectedFormat; + private String mNumBytesSelectedFormat; + private MemoryMearurementAdapter mAdapter; + private LayoutInflater mInflater; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setFinishOnTouchOutside(true); + setTitle(R.string.misc_files); + mNumSelectedFormat = getString(R.string.misc_files_selected_count); + mNumBytesSelectedFormat = getString(R.string.misc_files_selected_count_bytes); + mAdapter = new MemoryMearurementAdapter(this); + mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + setContentView(R.layout.settings_storage_miscfiles_list); + ListView lv = getListView(); + lv.setItemsCanFocus(true); + lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + lv.setMultiChoiceModeListener(new ModeCallback(this)); + setListAdapter(mAdapter); + } + + private class ModeCallback implements ListView.MultiChoiceModeListener { + private int mDataCount; + private final Context mContext; + + public ModeCallback(Context context) { + mContext = context; + mDataCount = mAdapter.getCount(); + } + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.misc_files_menu, menu); + return true; + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return true; + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + ListView lv = getListView(); + switch (item.getItemId()) { + case R.id.action_delete: + // delete the files selected + SparseBooleanArray checkedItems = lv.getCheckedItemPositions(); + int checkedCount = getListView().getCheckedItemCount(); + if (checkedCount > mDataCount) { + throw new IllegalStateException("checked item counts do not match. " + + "checkedCount: " + checkedCount + ", dataSize: " + mDataCount); + } + if (mDataCount > 0) { + ArrayList<Object> toRemove = new ArrayList<Object>(); + for (int i = 0; i < mDataCount; i++) { + if (!checkedItems.get(i)) { + //item not selected + continue; + } + if (MemoryMeasurement.LOGV) { + Log.i(TAG, "deleting: " + mAdapter.getItem(i)); + } + // delete the file + File file = new File(mAdapter.getItem(i).mFileName); + if (file.isDirectory()) { + deleteDir(file); + } else { + file.delete(); + } + toRemove.add(mAdapter.getItem(i)); + } + mAdapter.removeAll(toRemove); + mAdapter.notifyDataSetChanged(); + mDataCount = mAdapter.getCount(); + } + mode.finish(); + break; + + case R.id.action_select_all: + // check ALL items + for (int i = 0; i < mDataCount; i++) { + lv.setItemChecked(i, true); + } + // update the title and subtitle with number selected and numberBytes selected + onItemCheckedStateChanged(mode, 1, 0, true); + break; + } + return true; + } + + // Deletes all files and subdirectories under given dir. + // Returns true if all deletions were successful. + // If a deletion fails, the method stops attempting to delete and returns false. + private boolean deleteDir(File dir) { + if (dir.isDirectory()) { + String[] children = dir.list(); + for (int i=0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + return false; + } + } + } + // The directory is now empty so delete it + return dir.delete(); + } + + public void onDestroyActionMode(ActionMode mode) { + } + + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + ListView lv = getListView(); + int numChecked = lv.getCheckedItemCount(); + mode.setTitle(String.format(mNumSelectedFormat, numChecked, mAdapter.getCount())); + + // total the sizes of all items selected so far + SparseBooleanArray checkedItems = lv.getCheckedItemPositions(); + long selectedDataSize = 0; + if (numChecked > 0) { + for (int i = 0; i < mDataCount; i++) { + if (checkedItems.get(i)) { + // item is checked + selectedDataSize += mAdapter.getItem(i).mSize; + } + } + } + mode.setSubtitle(String.format(mNumBytesSelectedFormat, + Formatter.formatFileSize(mContext, selectedDataSize), + Formatter.formatFileSize(mContext, mAdapter.getDataSize()))); + } + } + + public class MemoryMearurementAdapter extends BaseAdapter { + private ArrayList<MemoryMeasurement.FileInfo> mData = null; + private long mDataSize = 0; + private Context mContext; + + public MemoryMearurementAdapter(Context context) { + mContext = context; + MemoryMeasurement mMeasurement = MemoryMeasurement.getInstance(context); + mData = (ArrayList<MemoryMeasurement.FileInfo>)mMeasurement.mFileInfoForMisc; + if (mData != null) { + for (MemoryMeasurement.FileInfo info : mData) { + mDataSize += info.mSize; + } + } + } + + @Override + public int getCount() { + return (mData == null) ? 0 : mData.size(); + } + + @Override + public MemoryMeasurement.FileInfo getItem(int position) { + if (mData == null || mData.size() <= position) { + return null; + } + return mData.get(position); + } + + @Override + public long getItemId(int position) { + if (mData == null || mData.size() <= position) { + return 0; + } + return mData.get(position).mId; + } + public void removeAll(List<Object> objs) { + if (mData == null) { + return; + } + for (Object o : objs) { + mData.remove(o); + mDataSize -= ((MemoryMeasurement.FileInfo) o).mSize; + } + } + + public long getDataSize() { + return mDataSize; + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final FileItemInfoLayout view = (convertView == null) ? + (FileItemInfoLayout) mInflater.inflate(R.layout.settings_storage_miscfiles, + parent, false) : (FileItemInfoLayout) convertView; + FileInfo item = getItem(position); + view.setFileName(item.mFileName); + view.setFileSize(Formatter.formatFileSize(mContext, item.mSize)); + final ListView listView = (ListView) parent; + final int listPosition = position; + view.getCheckBox().setOnCheckedChangeListener(new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + listView.setItemChecked(listPosition, isChecked); + } + + }); + view.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (listView.getCheckedItemCount() > 0) { + return false; + } + listView.setItemChecked(listPosition, !view.isChecked()); + return true; + } + }); + view.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (listView.getCheckedItemCount() > 0) { + listView.setItemChecked(listPosition, !view.isChecked()); + } + } + }); + return view; + } + } +}
\ No newline at end of file diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java index 9e3b47c30..999c765f7 100644 --- a/src/com/android/settings/deviceinfo/Status.java +++ b/src/com/android/settings/deviceinfo/Status.java @@ -409,7 +409,7 @@ public class Status extends PreferenceActivity { if (-1 == signalDbm) signalDbm = 0; - int signalAsu = mPhoneStateReceiver.getSignalStrength(); + int signalAsu = mPhoneStateReceiver.getSignalStrengthLevelAsu(); if (-1 == signalAsu) signalAsu = 0; diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index fa626faa6..fc903eb33 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -42,6 +42,7 @@ import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; +import android.telephony.SignalStrength; import android.util.Log; import android.util.SparseArray; import android.view.Menu; @@ -572,7 +573,7 @@ public class PowerUsageSummary extends PreferenceFragment implements Runnable { private void addRadioUsage(long uSecNow) { double power = 0; - final int BINS = BatteryStats.NUM_SIGNAL_STRENGTH_BINS; + final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; long signalTimeMs = 0; for (int i = 0; i < BINS; i++) { long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, uSecNow, mStatsType) / 1000; diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java index 80030418a..362fbb5bd 100644 --- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java +++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java @@ -159,7 +159,7 @@ public class InputMethodAndSubtypeUtil { HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver); final boolean onlyOneIME = inputMethodInfos.size() == 1; - boolean existsSelectedSubtype = false; + boolean needsToResetSelectedSubtype = false; for (InputMethodInfo imi : inputMethodInfos) { final String imiId = imi.getId(); Preference pref = context.findPreference(imiId); @@ -178,7 +178,7 @@ public class InputMethodAndSubtypeUtil { } HashSet<String> subtypesSet = enabledIMEAndSubtypesMap.get(imiId); - boolean subtypeCleared = false; + boolean subtypePrefFound = false; final int subtypeCount = imi.getSubtypeCount(); for (int i = 0; i < subtypeCount; ++i) { InputMethodSubtype subtype = imi.getSubtypeAt(i); @@ -187,17 +187,21 @@ public class InputMethodAndSubtypeUtil { imiId + subtypeHashCodeStr); // In the Configure input method screen which does not have subtype preferences. if (subtypePref == null) continue; - // Once subtype checkbox is found, subtypeSet needs to be cleared. - // Because of system change, hashCode value could have been changed. - if (!subtypeCleared) { + if (!subtypePrefFound) { + // Once subtype checkbox is found, subtypeSet needs to be cleared. + // Because of system change, hashCode value could have been changed. subtypesSet.clear(); - subtypeCleared = true; + // If selected subtype preference is disabled, needs to reset. + needsToResetSelectedSubtype = true; + subtypePrefFound = true; } if (subtypePref.isChecked()) { subtypesSet.add(subtypeHashCodeStr); if (isCurrentInputMethod) { if (selectedInputMethodSubtype == subtype.hashCode()) { - existsSelectedSubtype = true; + // Selected subtype is still enabled, there is no need to reset + // selected subtype. + needsToResetSelectedSubtype = false; } } } else { @@ -241,12 +245,14 @@ public class InputMethodAndSubtypeUtil { Log.d(TAG, "--- Save disable system inputmethod settings. :" + disabledSysImesBuilder.toString()); Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId); + Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype); + Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver)); } // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype // selected. And if the selected subtype of the current input method was disabled, // We should reset the selected input method's subtype. - if (!existsSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) { + if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) { if (DEBUG) { Log.d(TAG, "--- Reset inputmethod subtype because it's not defined."); } diff --git a/src/com/android/settings/inputmethod/InputMethodConfig.java b/src/com/android/settings/inputmethod/InputMethodConfig.java index f2bdafd47..2cfe35d5f 100644 --- a/src/com/android/settings/inputmethod/InputMethodConfig.java +++ b/src/com/android/settings/inputmethod/InputMethodConfig.java @@ -104,31 +104,28 @@ public class InputMethodConfig extends SettingsPreferenceFragment { private void showSecurityWarnDialog(InputMethodInfo imi, final CheckBoxPreference chkPref, final String imiId) { - if (mDialog == null) { - mDialog = (new AlertDialog.Builder(getActivity())) - .setTitle(android.R.string.dialog_alert_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - chkPref.setChecked(true); - for (Preference pref: mInputMethodPrefsMap.get(imiId)) { - pref.setEnabled(true); - } - } - }) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - } - }) - .create(); - } else { - if (mDialog.isShowing()) { - mDialog.dismiss(); - } + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); } + mDialog = (new AlertDialog.Builder(getActivity())) + .setTitle(android.R.string.dialog_alert_title) + .setIcon(android.R.drawable.ic_dialog_alert) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + chkPref.setChecked(true); + for (Preference pref: mInputMethodPrefsMap.get(imiId)) { + pref.setEnabled(true); + } + } + }) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + } + }) + .create(); mDialog.setMessage(getResources().getString(R.string.ime_security_warning, imi.getServiceInfo().applicationInfo.loadLabel(getPackageManager()))); mDialog.show(); diff --git a/src/com/android/settings/vpn/VpnEditor.java b/src/com/android/settings/vpn/VpnEditor.java index f4f5828ea..d36279397 100644 --- a/src/com/android/settings/vpn/VpnEditor.java +++ b/src/com/android/settings/vpn/VpnEditor.java @@ -22,6 +22,7 @@ import com.android.settings.SettingsPreferenceFragment; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.vpn.L2tpIpsecProfile; @@ -152,17 +153,15 @@ public class VpnEditor extends SettingsPreferenceFragment { }*/ private void initViewFor(VpnProfile profile) { - setTitle(profile); mProfileEditor.loadPreferencesTo(getPreferenceScreen()); } - private void setTitle(VpnProfile profile) { - final Activity activity = getActivity(); - String formatString = mAddingProfile - ? activity.getString(R.string.vpn_edit_title_add) - : activity.getString(R.string.vpn_edit_title_edit); - activity.setTitle(String.format(formatString, - profile.getType().getDisplayName())); + /* package */static String getTitle(Context context, VpnProfile profile, boolean adding) { + String formatString = adding + ? context.getString(R.string.vpn_edit_title_add) + : context.getString(R.string.vpn_edit_title_edit); + return String.format(formatString, + profile.getType().getDisplayName()); } /** diff --git a/src/com/android/settings/vpn/VpnSettings.java b/src/com/android/settings/vpn/VpnSettings.java index 539a51ec5..e62a5ee09 100644 --- a/src/com/android/settings/vpn/VpnSettings.java +++ b/src/com/android/settings/vpn/VpnSettings.java @@ -683,7 +683,7 @@ public class VpnSettings extends SettingsPreferenceFragment } private void startVpnTypeSelection() { - if (getActivity() == null) return; + if ((getActivity() == null) || isRemoving()) return; ((PreferenceActivity) getActivity()).startPreferencePanel( VpnTypeSelection.class.getCanonicalName(), null, R.string.vpn_type_title, null, @@ -738,14 +738,14 @@ public class VpnSettings extends SettingsPreferenceFragment } private void startVpnEditor(final VpnProfile profile, boolean add) { - if (getActivity() == null) return; + if ((getActivity() == null) || isRemoving()) return; Bundle args = new Bundle(); args.putParcelable(KEY_VPN_PROFILE, profile); // TODO: Show different titles for add and edit. ((PreferenceActivity)getActivity()).startPreferencePanel( VpnEditor.class.getCanonicalName(), args, - add ? R.string.vpn_details_title : R.string.vpn_details_title, null, + 0, VpnEditor.getTitle(getActivity(), profile, add), this, REQUEST_ADD_OR_EDIT_PROFILE); } diff --git a/src/com/android/settings/widget/SettingsAppWidgetProvider.java b/src/com/android/settings/widget/SettingsAppWidgetProvider.java index 8f17e05e6..ba2b61541 100644 --- a/src/com/android/settings/widget/SettingsAppWidgetProvider.java +++ b/src/com/android/settings/widget/SettingsAppWidgetProvider.java @@ -38,6 +38,7 @@ import android.provider.Settings; import android.util.Log; import android.widget.RemoteViews; import com.android.settings.R; +import com.android.settings.bluetooth.LocalBluetoothAdapter; import com.android.settings.bluetooth.LocalBluetoothManager; /** @@ -50,7 +51,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { new ComponentName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); - private static LocalBluetoothManager sLocalBluetoothManager = null; + private static LocalBluetoothAdapter sLocalBluetoothAdapter = null; private static final int BUTTON_WIFI = 0; private static final int BUTTON_BRIGHTNESS = 1; @@ -411,18 +412,19 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { @Override public int getActualState(Context context) { - if (sLocalBluetoothManager == null) { - sLocalBluetoothManager = LocalBluetoothManager.getInstance(context); - if (sLocalBluetoothManager == null) { + if (sLocalBluetoothAdapter == null) { + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context); + if (manager == null) { return STATE_UNKNOWN; // On emulator? } + sLocalBluetoothAdapter = manager.getBluetoothAdapter(); } - return bluetoothStateToFiveState(sLocalBluetoothManager.getBluetoothState()); + return bluetoothStateToFiveState(sLocalBluetoothAdapter.getBluetoothState()); } @Override protected void requestStateChange(Context context, final boolean desiredState) { - if (sLocalBluetoothManager == null) { + if (sLocalBluetoothAdapter == null) { Log.d(TAG, "No LocalBluetoothManager"); return; } @@ -433,7 +435,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... args) { - sLocalBluetoothManager.setBluetoothEnabled(desiredState); + sLocalBluetoothAdapter.setBluetoothEnabled(desiredState); return null; } }.execute(); @@ -584,7 +586,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Update each requested appWidgetId - RemoteViews view = buildUpdate(context, -1); + RemoteViews view = buildUpdate(context); for (int i = 0; i < appWidgetIds.length; i++) { appWidgetManager.updateAppWidget(appWidgetIds[i], view); @@ -613,22 +615,22 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { /** * Load image for given widget and build {@link RemoteViews} for it. */ - static RemoteViews buildUpdate(Context context, int appWidgetId) { + static RemoteViews buildUpdate(Context context) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget); - views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, appWidgetId, + views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, BUTTON_WIFI)); views.setOnClickPendingIntent(R.id.btn_brightness, getLaunchPendingIntent(context, - appWidgetId, BUTTON_BRIGHTNESS)); + BUTTON_BRIGHTNESS)); views.setOnClickPendingIntent(R.id.btn_sync, getLaunchPendingIntent(context, - appWidgetId, BUTTON_SYNC)); + BUTTON_SYNC)); views.setOnClickPendingIntent(R.id.btn_gps, - getLaunchPendingIntent(context, appWidgetId, BUTTON_GPS)); + getLaunchPendingIntent(context, BUTTON_GPS)); views.setOnClickPendingIntent(R.id.btn_bluetooth, getLaunchPendingIntent(context, - appWidgetId, BUTTON_BLUETOOTH)); + BUTTON_BLUETOOTH)); updateButtons(views, context); return views; @@ -640,7 +642,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { * @param context */ public static void updateWidget(Context context) { - RemoteViews views = buildUpdate(context, -1); + RemoteViews views = buildUpdate(context); // Update specific list of appWidgetIds if given, otherwise default to all final AppWidgetManager gm = AppWidgetManager.getInstance(context); gm.updateAppWidget(THIS_APPWIDGET, views); @@ -680,10 +682,9 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { * Creates PendingIntent to notify the widget of a button click. * * @param context - * @param appWidgetId * @return */ - private static PendingIntent getLaunchPendingIntent(Context context, int appWidgetId, + private static PendingIntent getLaunchPendingIntent(Context context, int buttonId) { Intent launchIntent = new Intent(); launchIntent.setClass(context, SettingsAppWidgetProvider.class); diff --git a/src/com/android/settings/wifi/WifiApDialog.java b/src/com/android/settings/wifi/WifiApDialog.java index fde6efc69..29c1a5df2 100644 --- a/src/com/android/settings/wifi/WifiApDialog.java +++ b/src/com/android/settings/wifi/WifiApDialog.java @@ -46,12 +46,13 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, private final DialogInterface.OnClickListener mListener; - private static final int OPEN_INDEX = 0; - private static final int WPA_INDEX = 1; + static final int OPEN_INDEX = 0; + static final int WPA_INDEX = 1; + static final int WPA2_INDEX = 2; private View mView; private TextView mSsid; - private int mSecurityType = AccessPoint.SECURITY_NONE; + private int mSecurityTypeIndex = OPEN_INDEX; private EditText mPassword; WifiConfiguration mWifiConfig; @@ -61,8 +62,18 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, super(context); mListener = listener; mWifiConfig = wifiConfig; - if (wifiConfig != null) - mSecurityType = AccessPoint.getSecurity(wifiConfig); + if (wifiConfig != null) { + mSecurityTypeIndex = getSecurityTypeIndex(wifiConfig); + } + } + + public static int getSecurityTypeIndex(WifiConfiguration wifiConfig) { + if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + return WPA_INDEX; + } else if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) { + return WPA2_INDEX; + } + return OPEN_INDEX; } public WifiConfiguration getConfig() { @@ -77,12 +88,12 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, */ config.SSID = mSsid.getText().toString(); - switch (mSecurityType) { - case AccessPoint.SECURITY_NONE: + switch (mSecurityTypeIndex) { + case OPEN_INDEX: config.allowedKeyManagement.set(KeyMgmt.NONE); return config; - case AccessPoint.SECURITY_PSK: + case WPA_INDEX: config.allowedKeyManagement.set(KeyMgmt.WPA_PSK); config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); if (mPassword.length() != 0) { @@ -90,6 +101,15 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, config.preSharedKey = password; } return config; + + case WPA2_INDEX: + config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK); + config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); + if (mPassword.length() != 0) { + String password = mPassword.getText().toString(); + config.preSharedKey = password; + } + return config; } return null; } @@ -116,15 +136,10 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, if (mWifiConfig != null) { mSsid.setText(mWifiConfig.SSID); - switch (mSecurityType) { - case AccessPoint.SECURITY_NONE: - mSecurity.setSelection(OPEN_INDEX); - break; - case AccessPoint.SECURITY_PSK: - String str = mWifiConfig.preSharedKey; - mPassword.setText(str); - mSecurity.setSelection(WPA_INDEX); - break; + mSecurity.setSelection(mSecurityTypeIndex); + if (mSecurityTypeIndex == WPA_INDEX || + mSecurityTypeIndex == WPA2_INDEX) { + mPassword.setText(mWifiConfig.preSharedKey); } } @@ -141,7 +156,8 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, private void validate() { if ((mSsid != null && mSsid.length() == 0) || - (mSecurityType == AccessPoint.SECURITY_PSK && mPassword.length() < 8)) { + (((mSecurityTypeIndex == WPA_INDEX) || (mSecurityTypeIndex == WPA2_INDEX))&& + mPassword.length() < 8)) { getButton(BUTTON_SUBMIT).setEnabled(false); } else { getButton(BUTTON_SUBMIT).setEnabled(true); @@ -167,10 +183,7 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - if(position == OPEN_INDEX) - mSecurityType = AccessPoint.SECURITY_NONE; - else - mSecurityType = AccessPoint.SECURITY_PSK; + mSecurityTypeIndex = position; showSecurityFields(); validate(); } @@ -180,7 +193,7 @@ class WifiApDialog extends AlertDialog implements View.OnClickListener, } private void showSecurityFields() { - if (mSecurityType == AccessPoint.SECURITY_NONE) { + if (mSecurityTypeIndex == OPEN_INDEX) { mView.findViewById(R.id.fields).setVisibility(View.GONE); return; } diff --git a/src/com/android/settings/wifi/WifiApEnabler.java b/src/com/android/settings/wifi/WifiApEnabler.java index e907cf70e..982e44e3c 100644 --- a/src/com/android/settings/wifi/WifiApEnabler.java +++ b/src/com/android/settings/wifi/WifiApEnabler.java @@ -66,6 +66,8 @@ public class WifiApEnabler implements Preference.OnPreferenceChangeListener { ArrayList<String> errored = intent.getStringArrayListExtra( ConnectivityManager.EXTRA_ERRORED_TETHER); updateTetherState(available.toArray(), active.toArray(), errored.toArray()); + } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { + enableWifiCheckBox(); } } @@ -84,6 +86,7 @@ public class WifiApEnabler implements Preference.OnPreferenceChangeListener { mIntentFilter = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); mIntentFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); + mIntentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); } public void resume() { @@ -103,6 +106,7 @@ public class WifiApEnabler implements Preference.OnPreferenceChangeListener { if(!isAirplaneMode) { mCheckBox.setEnabled(true); } else { + mCheckBox.setSummary(mOriginalSummary); mCheckBox.setEnabled(false); } } diff --git a/src/com/android/settings/wifi/WifiApSettings.java b/src/com/android/settings/wifi/WifiApSettings.java index 7336c6c5e..ddc3c5c14 100644 --- a/src/com/android/settings/wifi/WifiApSettings.java +++ b/src/com/android/settings/wifi/WifiApSettings.java @@ -41,9 +41,6 @@ public class WifiApSettings extends SettingsPreferenceFragment private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; private static final int CONFIG_SUBTEXT = R.string.wifi_tether_configure_subtext; - private static final int OPEN_INDEX = 0; - private static final int WPA_INDEX = 1; - private static final int DIALOG_AP_SETTINGS = 1; private String[] mSecurityType; @@ -81,12 +78,12 @@ public class WifiApSettings extends SettingsPreferenceFragment final String s = activity.getString( com.android.internal.R.string.wifi_tether_configure_ssid_default); mCreateNetwork.setSummary(String.format(activity.getString(CONFIG_SUBTEXT), - s, mSecurityType[OPEN_INDEX])); + s, mSecurityType[WifiApDialog.OPEN_INDEX])); } else { + int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig); mCreateNetwork.setSummary(String.format(activity.getString(CONFIG_SUBTEXT), mWifiConfig.SSID, - mWifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? - mSecurityType[WPA_INDEX] : mSecurityType[OPEN_INDEX])); + mSecurityType[index])); } } @@ -138,10 +135,10 @@ public class WifiApSettings extends SettingsPreferenceFragment } else { mWifiManager.setWifiApConfiguration(mWifiConfig); } + int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig); mCreateNetwork.setSummary(String.format(getActivity().getString(CONFIG_SUBTEXT), mWifiConfig.SSID, - mWifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? - mSecurityType[WPA_INDEX] : mSecurityType[OPEN_INDEX])); + mSecurityType[index])); } } } diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java index 750e168b2..7ab647e11 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -159,7 +159,7 @@ public class WifiConfigController implements TextWatcher, mSsidView.addTextChangedListener(this); mSecuritySpinner = ((Spinner) mView.findViewById(R.id.security)); mSecuritySpinner.setOnItemSelectedListener(this); - if (context instanceof WifiSettingsForSetupWizardXL) { + if (mInXlSetupWizard) { // We want custom layout. The content must be same as the other cases. mSecuritySpinner.setAdapter( new ArrayAdapter<String>(context, R.layout.wifi_setup_custom_list_item_1, @@ -173,9 +173,6 @@ public class WifiConfigController implements TextWatcher, mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings); mIpSettingsSpinner.setOnItemSelectedListener(this); mProxySettingsSpinner = (Spinner) mView.findViewById(R.id.proxy_settings); - // disable proxy UI until we have better app support - mProxySettingsSpinner.setVisibility(View.GONE); - mView.findViewById(R.id.proxy_settings_title).setVisibility(View.GONE); mProxySettingsSpinner.setOnItemSelectedListener(this); ViewGroup group = (ViewGroup) mView.findViewById(R.id.info); @@ -405,46 +402,48 @@ public class WifiConfigController implements TextWatcher, } private int validateIpConfigFields(LinkProperties linkProperties) { + String ipAddr = mIpAddressView.getText().toString(); + InetAddress inetAddr = null; try { - String ipAddr = mIpAddressView.getText().toString(); - if (!InetAddress.isNumeric(ipAddr)) { - return R.string.wifi_ip_settings_invalid_ip_address; - } - InetAddress inetAddr = InetAddress.getByName(ipAddr); - - int networkPrefixLength = Integer.parseInt(mNetworkPrefixLengthView.getText() - .toString()); - if (networkPrefixLength < 0 || networkPrefixLength > 32) { - return R.string.wifi_ip_settings_invalid_network_prefix_length; - } + inetAddr = NetworkUtils.numericToInetAddress(ipAddr); + } catch (IllegalArgumentException e) { + return R.string.wifi_ip_settings_invalid_ip_address; + } - linkProperties.addLinkAddress(new LinkAddress(inetAddr, networkPrefixLength)); + int networkPrefixLength = -1; + try { + networkPrefixLength = Integer.parseInt(mNetworkPrefixLengthView.getText().toString()); + } catch (NumberFormatException e) { } + if (networkPrefixLength < 0 || networkPrefixLength > 32) { + return R.string.wifi_ip_settings_invalid_network_prefix_length; + } + linkProperties.addLinkAddress(new LinkAddress(inetAddr, networkPrefixLength)); - String gateway = mGatewayView.getText().toString(); - if (!InetAddress.isNumeric(gateway)) { - return R.string.wifi_ip_settings_invalid_gateway; - } - linkProperties.setGateway(InetAddress.getByName(gateway)); + String gateway = mGatewayView.getText().toString(); + InetAddress gatewayAddr = null; + try { + gatewayAddr = NetworkUtils.numericToInetAddress(gateway); + } catch (IllegalArgumentException e) { + return R.string.wifi_ip_settings_invalid_gateway; + } + linkProperties.addGateway(gatewayAddr); - String dns = mDns1View.getText().toString(); - if (!InetAddress.isNumeric(dns)) { + String dns = mDns1View.getText().toString(); + InetAddress dnsAddr = null; + try { + dnsAddr = NetworkUtils.numericToInetAddress(dns); + } catch (IllegalArgumentException e) { + return R.string.wifi_ip_settings_invalid_dns; + } + linkProperties.addDns(dnsAddr); + if (mDns2View.length() > 0) { + dns = mDns2View.getText().toString(); + try { + dnsAddr = NetworkUtils.numericToInetAddress(dns); + } catch (IllegalArgumentException e) { return R.string.wifi_ip_settings_invalid_dns; } - linkProperties.addDns(InetAddress.getByName(dns)); - if (mDns2View.length() > 0) { - dns = mDns2View.getText().toString(); - if (!InetAddress.isNumeric(dns)) { - return R.string.wifi_ip_settings_invalid_dns; - } - linkProperties.addDns(InetAddress.getByName(dns)); - } - - } catch (NumberFormatException ignore) { - return R.string.wifi_ip_settings_invalid_network_prefix_length; - } catch (UnknownHostException e) { - //Should not happen since we have already validated addresses - Log.e(TAG, "Failure to validate IP configuration " + e); - return R.string.wifi_ip_settings_invalid_ip_address; + linkProperties.addDns(dnsAddr); } return 0; } @@ -485,16 +484,16 @@ public class WifiConfigController implements TextWatcher, } private void showSecurityFields() { + if (mInXlSetupWizard) { + // Note: XL SetupWizard won't hide "EAP" settings here. + if (!((WifiSettingsForSetupWizardXL)mConfigUi.getContext()).initSecurityFields(mView, + mAccessPointSecurity)) { + return; + } + } if (mAccessPointSecurity == AccessPoint.SECURITY_NONE) { mView.findViewById(R.id.security_fields).setVisibility(View.GONE); return; - } else if (mAccessPointSecurity == AccessPoint.SECURITY_EAP && mInXlSetupWizard) { - // In SetupWizard for XLarge screen, we don't have enough space for showing - // configurations needed for EAP. We instead disable the whole feature there and let - // users configure those networks after the setup. - mView.findViewById(R.id.eap_not_supported).setVisibility(View.VISIBLE); - mView.findViewById(R.id.security_fields).setVisibility(View.GONE); - return; } mView.findViewById(R.id.security_fields).setVisibility(View.VISIBLE); @@ -594,9 +593,10 @@ public class WifiConfigController implements TextWatcher, mNetworkPrefixLengthView.setText(Integer.toString(linkAddress .getNetworkPrefixLength())); } - InetAddress gateway = linkProperties.getGateway(); - if (gateway != null) { - mGatewayView.setText(linkProperties.getGateway().getHostAddress()); + + Iterator<InetAddress>gateways = linkProperties.getGateways().iterator(); + if (gateways.hasNext()) { + mGatewayView.setText(gateways.next().getHostAddress()); } Iterator<InetAddress> dnsIterator = linkProperties.getDnses().iterator(); if (dnsIterator.hasNext()) { @@ -621,6 +621,7 @@ public class WifiConfigController implements TextWatcher, } if (mProxySettingsSpinner.getSelectedItemPosition() == PROXY_STATIC) { + mView.findViewById(R.id.proxy_warning_limited_support).setVisibility(View.VISIBLE); mView.findViewById(R.id.proxy_fields).setVisibility(View.VISIBLE); if (mProxyHostView == null) { mProxyHostView = (TextView) mView.findViewById(R.id.proxy_hostname); @@ -636,6 +637,7 @@ public class WifiConfigController implements TextWatcher, } } } else { + mView.findViewById(R.id.proxy_warning_limited_support).setVisibility(View.GONE); mView.findViewById(R.id.proxy_fields).setVisibility(View.GONE); } } diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index 5a2bf45d5..b62142680 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -18,11 +18,6 @@ package com.android.settings.wifi; import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; -import com.android.settings.ProgressCategoryBase; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.Utils; - import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; @@ -53,6 +48,7 @@ import android.provider.Settings.Secure; import android.provider.Settings; import android.security.Credentials; import android.security.KeyStore; +import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; @@ -64,6 +60,12 @@ import android.view.ContextMenu.ContextMenuInfo; import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo; +import com.android.internal.util.AsyncChannel; +import com.android.settings.ProgressCategoryBase; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.Utils; + import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -82,6 +84,7 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class WifiSettings extends SettingsPreferenceFragment implements DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener { + private static final String TAG = "WifiSettings"; private static final int MENU_ID_SCAN = Menu.FIRST; private static final int MENU_ID_ADVANCED = Menu.FIRST + 1; private static final int MENU_ID_CONNECT = Menu.FIRST + 2; @@ -168,6 +171,7 @@ public class WifiSettings extends SettingsPreferenceFragment // this method. mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + mWifiManager.asyncConnect(getActivity(), new WifiServiceHandler()); final Activity activity = getActivity(); final Intent intent = activity.getIntent(); @@ -238,6 +242,7 @@ public class WifiSettings extends SettingsPreferenceFragment if (mWifiEnabler != null) { mWifiEnabler.resume(); } + getActivity().registerReceiver(mReceiver, mFilter); if (mKeyStoreNetworkId != INVALID_NETWORK_ID && KeyStore.getInstance().test() == KeyStore.NO_ERROR) { @@ -594,6 +599,50 @@ public class WifiSettings extends SettingsPreferenceFragment } } + private class WifiServiceHandler extends Handler { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + //AsyncChannel in msg.obj + } else { + //AsyncChannel set up failure, ignore + Log.e(TAG, "Failed to establish AsyncChannel connection"); + } + break; + case WifiManager.CMD_WPS_COMPLETED: + WpsResult result = (WpsResult) msg.obj; + if (result == null) break; + AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.wifi_wps_setup_title) + .setPositiveButton(android.R.string.ok, null); + switch (result.status) { + case FAILURE: + dialog.setMessage(R.string.wifi_wps_failed); + dialog.show(); + break; + case IN_PROGRESS: + dialog.setMessage(R.string.wifi_wps_in_progress); + dialog.show(); + break; + default: + if (result.pin != null) { + dialog.setMessage(getResources().getString( + R.string.wifi_wps_pin_output, result.pin)); + dialog.show(); + } + break; + } + //TODO: more connectivity feedback + default: + //Ignore + break; + } + } + } + /** * Renames/replaces "Next" button when appropriate. "Next" button usually exists in * Wifi setup screens, not in usual wifi settings screen. @@ -631,27 +680,7 @@ public class WifiSettings extends SettingsPreferenceFragment case WifiConfigController.WPS_PBC: case WifiConfigController.WPS_PIN_FROM_ACCESS_POINT: case WifiConfigController.WPS_PIN_FROM_DEVICE: - WpsResult result = mWifiManager.startWps(configController.getWpsConfig()); - AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.wifi_wps_setup_title) - .setPositiveButton(android.R.string.ok, null); - switch (result.status) { - case FAILURE: - dialog.setMessage(R.string.wifi_wps_failed); - dialog.show(); - break; - case IN_PROGRESS: - dialog.setMessage(R.string.wifi_wps_in_progress); - dialog.show(); - break; - default: - if (networkSetup == WifiConfigController.WPS_PIN_FROM_DEVICE) { - dialog.setMessage(getResources().getString(R.string.wifi_wps_pin_output, - result.pin)); - dialog.show(); - } - break; - } + mWifiManager.startWps(configController.getWpsConfig()); break; case WifiConfigController.MANUAL: final WifiConfiguration config = configController.getConfig(); diff --git a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java index caafabc1f..e8a6a2594 100644 --- a/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java +++ b/src/com/android/settings/wifi/WifiSettingsForSetupWizardXL.java @@ -26,6 +26,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; +import android.os.Message; import android.preference.PreferenceCategory; import android.text.TextUtils; import android.util.Log; @@ -35,49 +36,54 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; -import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.internal.util.AsyncChannel; + import java.util.Collection; import java.util.EnumMap; +import java.util.List; /** * WifiSetings Activity specific for SetupWizard with X-Large screen size. */ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickListener { private static final String TAG = "SetupWizard"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; - private static final EnumMap<DetailedState, DetailedState> stateMap = + private static final EnumMap<DetailedState, DetailedState> sNetworkStateMap = new EnumMap<DetailedState, DetailedState>(DetailedState.class); static { - stateMap.put(DetailedState.IDLE, DetailedState.DISCONNECTED); - stateMap.put(DetailedState.SCANNING, DetailedState.SCANNING); - stateMap.put(DetailedState.CONNECTING, DetailedState.CONNECTING); - stateMap.put(DetailedState.AUTHENTICATING, DetailedState.CONNECTING); - stateMap.put(DetailedState.OBTAINING_IPADDR, DetailedState.CONNECTING); - stateMap.put(DetailedState.CONNECTED, DetailedState.CONNECTED); - stateMap.put(DetailedState.SUSPENDED, DetailedState.SUSPENDED); // ? - stateMap.put(DetailedState.DISCONNECTING, DetailedState.DISCONNECTED); - stateMap.put(DetailedState.DISCONNECTED, DetailedState.DISCONNECTED); - stateMap.put(DetailedState.FAILED, DetailedState.FAILED); + sNetworkStateMap.put(DetailedState.IDLE, DetailedState.DISCONNECTED); + sNetworkStateMap.put(DetailedState.SCANNING, DetailedState.SCANNING); + sNetworkStateMap.put(DetailedState.CONNECTING, DetailedState.CONNECTING); + sNetworkStateMap.put(DetailedState.AUTHENTICATING, DetailedState.CONNECTING); + sNetworkStateMap.put(DetailedState.OBTAINING_IPADDR, DetailedState.CONNECTING); + sNetworkStateMap.put(DetailedState.CONNECTED, DetailedState.CONNECTED); + sNetworkStateMap.put(DetailedState.SUSPENDED, DetailedState.SUSPENDED); // ? + sNetworkStateMap.put(DetailedState.DISCONNECTING, DetailedState.DISCONNECTED); + sNetworkStateMap.put(DetailedState.DISCONNECTED, DetailedState.DISCONNECTED); + sNetworkStateMap.put(DetailedState.FAILED, DetailedState.FAILED); } - private WifiManager mWifiManager; - /** - * Used for resizing a padding above title. Hiden when software keyboard is shown. + * Used with {@link Button#setTag(Object)} to remember "Connect" button is pressed in + * with "add network" flow. */ + private static final int CONNECT_BUTTON_TAG_ADD_NETWORK = 1; + + private WifiSettings mWifiSettings; + private WifiManager mWifiManager; + + /** Used for resizing a padding above title. Hiden when software keyboard is shown. */ private View mTopPadding; - /** - * Used for resizing a padding inside Config UI. Hiden when software keyboard is shown. - */ - private View mWifiConfigPadding; + /** Used for resizing a padding of main content. Hiden when software keyboard is shown. */ + private View mContentPadding; private TextView mTitleView; /** @@ -88,81 +94,91 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis private CharSequence mEditingTitle; private ProgressBar mProgressBar; - private WifiSettings mWifiSettings; + private View mTopDividerNoProgress; + /** + * Used for resizing a padding between WifiSettings preference and bottom bar when + * ProgressBar is visible as a top divider. + */ + private View mBottomPadding; private Button mAddNetworkButton; private Button mRefreshButton; private Button mSkipOrNextButton; private Button mBackButton; - private static int CONNECT_BUTTON_TAG_ADD_NETWORK = 1; - private Button mConnectButton; + /** + * View enclosing {@link WifiSettings}. + */ + private View mWifiSettingsFragmentLayout; private View mConnectingStatusLayout; private TextView mConnectingStatusView; - // true when a user already pressed "Connect" button and waiting for connection. - // Also true when the device is already connected to a wifi network on launch. - private boolean mAfterConnectAction; + /* + * States of current screen, which should be saved and restored when Activity is relaunched + * with orientation change, etc. + */ + private static final int SCREEN_STATE_DISCONNECTED = 0; + private static final int SCREEN_STATE_EDITING = 1; + private static final int SCREEN_STATE_CONNECTING = 2; + private static final int SCREEN_STATE_CONNECTED = 3; + + /** Current screen state. */ + private int mScreenState = SCREEN_STATE_DISCONNECTED; private WifiConfigUiForSetupWizardXL mWifiConfig; private InputMethodManager mInputMethodManager; - private final Handler mHandler = new Handler(); - - private int mPreviousWpsFieldsVisibility = View.GONE; - private int mPreviousSecurityFieldsVisibility = View.GONE; - private int mPreviousTypeVisibility = View.GONE; - - private DetailedState mPreviousState = DetailedState.DISCONNECTED; + /** + * Previous network connection state reported by main Wifi module. + * + * Note that we don't use original {@link DetailedState} object but simplified one translated + * using sNetworkStateMap. + */ + private DetailedState mPreviousNetworkState = DetailedState.DISCONNECTED; private int mBackgroundId = R.drawable.setups_bg_default; - // At first, we set "Skip" button disabled so that users won't press it soon after the screen - // migration. The button is enabled after the wifi module returns some result - // (a list of available network, etc.) One possible problem is that the notification from the - // wifi module may be delayed and users may be stuck here, without any other way to exit this - // screen. - // To let users exit this Activity, we enable the button after waiting for a moment. - private final int DELAYED_SKIP_ENABLE_TIME = 10000; // Unit: millis - private final Runnable mSkipButtonEnabler = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Delayed skip enabler starts running."); - mSkipOrNextButton.setEnabled(true); - } - }; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.wifi_settings_for_setup_wizard_xl); - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - mWifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE); // There's no button here enabling wifi network, so we need to enable it without // users' request. mWifiManager.setWifiEnabled(true); + mWifiManager.asyncConnect(this, new WifiServiceHandler()); mWifiSettings = (WifiSettings)getFragmentManager().findFragmentById(R.id.wifi_setup_fragment); mInputMethodManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - setup(); + + initViews(); + + // At first, Wifi module doesn't return SCANNING state (it's too early), so we manually + // show it. + showScanningProgressBar(); } - public void setup() { - final View layoutRoot = findViewById(R.id.layout_root); - layoutRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK); + private void initViews() { + if (getIntent().getBooleanExtra("firstRun", false)) { + final View layoutRoot = findViewById(R.id.layout_root); + layoutRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK); + } mTitleView = (TextView)findViewById(R.id.wifi_setup_title); mProgressBar = (ProgressBar)findViewById(R.id.scanning_progress_bar); mProgressBar.setMax(2); + mTopDividerNoProgress = findViewById(R.id.top_divider_no_progress); + mBottomPadding = findViewById(R.id.bottom_padding); + mProgressBar.setVisibility(View.VISIBLE); mProgressBar.setIndeterminate(true); + mTopDividerNoProgress.setVisibility(View.GONE); mAddNetworkButton = (Button)findViewById(R.id.wifi_setup_add_network); mAddNetworkButton.setOnClickListener(this); @@ -176,27 +192,40 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis mBackButton.setOnClickListener(this); mTopPadding = findViewById(R.id.top_padding); - mWifiConfigPadding = findViewById(R.id.wifi_config_padding); + mContentPadding = findViewById(R.id.content_padding); + mWifiSettingsFragmentLayout = findViewById(R.id.wifi_settings_fragment_layout); mConnectingStatusLayout = findViewById(R.id.connecting_status_layout); mConnectingStatusView = (TextView) findViewById(R.id.connecting_status); + } - // At first, Wifi module doesn't return SCANNING state (it's too early), so we manually - // show it. - showScanningStatus(); - mHandler.postDelayed(mSkipButtonEnabler, DELAYED_SKIP_ENABLE_TIME); + private class WifiServiceHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + //AsyncChannel in msg.obj + } else { + //AsyncChannel set up failure, ignore + Log.e(TAG, "Failed to establish AsyncChannel connection"); + } + break; + default: + //Ignore + break; + } + } } - private void restoreFirstButtonVisibilityState() { + private void restoreFirstVisibilityState() { showDefaultTitle(); - // TODO: uncomment this when the layout for it is ready. Note that we also have to remove - // android:visibility="gone" in xml then. - // mAddNetworkButton.setVisibility(View.VISIBLE); + mAddNetworkButton.setVisibility(View.VISIBLE); mRefreshButton.setVisibility(View.VISIBLE); mSkipOrNextButton.setVisibility(View.VISIBLE); mConnectButton.setVisibility(View.GONE); mBackButton.setVisibility(View.GONE); - setPaddingVisibility(View.VISIBLE, View.GONE); + setPaddingVisibility(View.VISIBLE); } @Override @@ -215,8 +244,8 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis // any access point. mWifiManager.setWifiEnabled(false); } - setResult(Activity.RESULT_OK); - finish(); + setResult(RESULT_OK); + finish(); } else if (view == mConnectButton) { if (DEBUG) Log.d(TAG, "Connect button pressed"); onConnectButtonPressed(); @@ -236,7 +265,7 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis // Called from WifiSettings /* package */ void updateConnectionState(DetailedState originalState) { - final DetailedState state = stateMap.get(originalState); + final DetailedState state = sNetworkStateMap.get(originalState); if (originalState == DetailedState.FAILED) { // We clean up the current connectivity status and let users select another network @@ -249,70 +278,82 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis // Let users know the device is working correctly though currently there's // no visible network on the list. if (mWifiSettings.getAccessPointsCount() == 0) { - mProgressBar.setIndeterminate(true); + showScanningState(); } else { - // Users already connected to a network, or see available networks. - mProgressBar.setIndeterminate(false); + // Users already see available networks. + showDisconnectedProgressBar(); + if (mScreenState == SCREEN_STATE_DISCONNECTED) { + mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); + mBottomPadding.setVisibility(View.GONE); + } } break; } case CONNECTING: { - showConnectingStatus(); + if (mScreenState == SCREEN_STATE_CONNECTING) { + showConnectingState(); + } break; } case CONNECTED: { - hideSoftwareKeyboard(); - setPaddingVisibility(View.VISIBLE); - - // If the device is already connected to a wifi without users' "Connect" request, - // this can be false here. We want to treat it as "after connect action". - mAfterConnectAction = true; - - trySetBackground(R.drawable.setups_bg_complete); - - mProgressBar.setIndeterminate(false); - mProgressBar.setProgress(2); - - showConnectedTitle(); - mConnectingStatusView.setText(R.string.wifi_setup_description_connected); - mConnectButton.setVisibility(View.GONE); - mAddNetworkButton.setVisibility(View.GONE); - mRefreshButton.setVisibility(View.GONE); - mBackButton.setVisibility(View.VISIBLE); - mBackButton.setText(R.string.wifi_setup_back); - mSkipOrNextButton.setVisibility(View.VISIBLE); - mSkipOrNextButton.setEnabled(true); - mHandler.removeCallbacks(mSkipButtonEnabler); + showConnectedState(); break; } default: // DISCONNECTED, FAILED - showDisconnectedStatus(Summary.get(this, state)); + if (mScreenState != SCREEN_STATE_CONNECTED) { + showDisconnectedState(Summary.get(this, state)); + } break; } - mPreviousState = state; + mPreviousNetworkState = state; } - private void showDisconnectedStatus(String stateString) { - mProgressBar.setIndeterminate(false); - mProgressBar.setProgress(0); - + private void showDisconnectedState(String stateString) { + showDisconnectedProgressBar(); + if (mScreenState == SCREEN_STATE_DISCONNECTED) { + mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); + mBottomPadding.setVisibility(View.GONE); + } mAddNetworkButton.setEnabled(true); mRefreshButton.setEnabled(true); } - private void showConnectingStatus() { + private void showConnectingState() { + mScreenState = SCREEN_STATE_CONNECTING; + mBackButton.setVisibility(View.VISIBLE); // We save this title and show it when authentication failed. mEditingTitle = mTitleView.getText(); showConnectingTitle(); - mProgressBar.setIndeterminate(false); - mProgressBar.setProgress(1); + showConnectingProgressBar(); - // We may enter "Connecting" status during editing password again (if the Wifi module - // tries to (re)connect a network.) - if (mAfterConnectAction) { - setPaddingVisibility(View.VISIBLE); - } + setPaddingVisibility(View.VISIBLE); + } + + private void showConnectedState() { + // Once we show "connected" screen, we won't change it even when the device becomes + // disconnected afterwards. We keep the state unless a user explicitly cancel it + // (by pressing "back" button). + mScreenState = SCREEN_STATE_CONNECTED; + + hideSoftwareKeyboard(); + setPaddingVisibility(View.VISIBLE); + + trySetBackground(R.drawable.setups_bg_complete); + showConnectedTitle(); + showConnectedProgressBar(); + + mWifiSettingsFragmentLayout.setVisibility(View.GONE); + mConnectingStatusLayout.setVisibility(View.VISIBLE); + + mConnectingStatusView.setText(R.string.wifi_setup_description_connected); + mConnectButton.setVisibility(View.GONE); + mAddNetworkButton.setVisibility(View.GONE); + mRefreshButton.setVisibility(View.GONE); + mBackButton.setVisibility(View.VISIBLE); + mBackButton.setText(R.string.wifi_setup_back); + mSkipOrNextButton.setVisibility(View.VISIBLE); + mSkipOrNextButton.setEnabled(true); } private void showDefaultTitle() { @@ -363,10 +404,23 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis mTitleView.setText(getString(R.string.wifi_setup_title_connected_network, mNetworkName)); } - private void showScanningStatus() { - mProgressBar.setIndeterminate(true); - mAddNetworkButton.setEnabled(false); - mRefreshButton.setEnabled(false); + /** + * Shows top divider with ProgressBar without defining the state of the ProgressBar. + * + * @see #showScanningProgressBar() + * @see #showConnectedProgressBar() + * @see #showConnectingProgressBar() + */ + private void showTopDividerWithProgressBar() { + mProgressBar.setVisibility(View.VISIBLE); + mTopDividerNoProgress.setVisibility(View.GONE); + mBottomPadding.setVisibility(View.GONE); + } + + private void showScanningState() { + setPaddingVisibility(View.VISIBLE); + mWifiSettingsFragmentLayout.setVisibility(View.GONE); + showScanningProgressBar(); } private void onAddNetworkButtonPressed() { @@ -381,6 +435,8 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis * "Add network" button, meaning there's no selected access point. */ /* package */ void showConfigUi(AccessPoint selectedAccessPoint, boolean edit) { + mScreenState = SCREEN_STATE_EDITING; + if (selectedAccessPoint != null && (selectedAccessPoint.security == AccessPoint.SECURITY_WEP || selectedAccessPoint.security == AccessPoint.SECURITY_PSK)) { @@ -392,23 +448,20 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis trySetBackground(R.drawable.setups_bg_default); - // We don't want to keep scanning Wi-Fi networks during users' configuring one network. + // We don't want to keep scanning Wifi networks during users' configuring a network. mWifiSettings.pauseWifiScan(); - findViewById(R.id.wifi_setup).setVisibility(View.GONE); + mWifiSettingsFragmentLayout.setVisibility(View.GONE); mConnectingStatusLayout.setVisibility(View.GONE); final ViewGroup parent = (ViewGroup)findViewById(R.id.wifi_config_ui); parent.setVisibility(View.VISIBLE); parent.removeAllViews(); mWifiConfig = new WifiConfigUiForSetupWizardXL(this, parent, selectedAccessPoint, edit); - // For safety, we forget the tag once. Tag will be updated in this method when needed. + // Tag will be updated in this method when needed. mConnectButton.setTag(null); if (selectedAccessPoint == null) { // "Add network" flow showAddNetworkTitle(); - if (mWifiConfig != null) { - mWifiConfig.getView().findViewById(R.id.wifi_general_info).setVisibility(View.GONE); - } mConnectButton.setVisibility(View.VISIBLE); mConnectButton.setTag(CONNECT_BUTTON_TAG_ADD_NETWORK); @@ -423,8 +476,7 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis showEditingTitle(); showEditingButtonState(); if (selectedAccessPoint.security == AccessPoint.SECURITY_EAP) { - mConnectButton.setVisibility(View.GONE); - mBackButton.setText(R.string.wifi_setup_back); + onEapNetworkSelected(); } else { mConnectButton.setVisibility(View.VISIBLE); @@ -437,6 +489,51 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis } } + /** + * Called before security fields are correctly set by {@link WifiConfigController}. + * + * @param view security field view + * @param accessPointSecurity type of security. e.g. AccessPoint.SECURITY_NONE + * @return true when it is ok for the caller to init security fields. false when + * all security fields are managed by this method, and thus the caller shouldn't touch them. + */ + /* package */ boolean initSecurityFields(View view, int accessPointSecurity) { + // Reset all states tweaked below. + view.findViewById(R.id.eap_not_supported).setVisibility(View.GONE); + view.findViewById(R.id.eap_not_supported_for_add_network).setVisibility(View.GONE); + view.findViewById(R.id.ssid_text).setVisibility(View.VISIBLE); + view.findViewById(R.id.ssid_layout).setVisibility(View.VISIBLE); + + if (accessPointSecurity == AccessPoint.SECURITY_EAP) { + hideSoftwareKeyboard(); + + // In SetupWizard for XLarge screen, we don't have enough space for showing + // configurations needed for EAP. We instead disable the whole feature there and let + // users configure those networks after the setup. + if (view.findViewById(R.id.type).getVisibility() == View.VISIBLE) { + view.findViewById(R.id.eap_not_supported_for_add_network) + .setVisibility(View.VISIBLE); + } else { + view.findViewById(R.id.eap_not_supported).setVisibility(View.VISIBLE); + } + view.findViewById(R.id.security_fields).setVisibility(View.GONE); + view.findViewById(R.id.ssid_text).setVisibility(View.GONE); + view.findViewById(R.id.ssid_layout).setVisibility(View.GONE); + onEapNetworkSelected(); + + // This method did init security fields by itself. The caller must not do it. + return false; + } + + // Let the caller init security fields. + return true; + } + + /* package */ void onEapNetworkSelected() { + mConnectButton.setVisibility(View.GONE); + mBackButton.setText(R.string.wifi_setup_back); + } + private void showEditingButtonState() { mSkipOrNextButton.setVisibility(View.GONE); mAddNetworkButton.setVisibility(View.GONE); @@ -446,48 +543,29 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis // May be called when user press "connect" button in WifiDialog /* package */ void onConnectButtonPressed() { - mAfterConnectAction = true; + mScreenState = SCREEN_STATE_CONNECTING; trySetBackground(R.drawable.setups_bg_wifi); mWifiSettings.submit(mWifiConfig.getController()); - // updateConnectionState() isn't called soon after the user's "connect" action, - // and the user still sees "not connected" message for a while, which looks strange. + // updateConnectionState() isn't called soon by the main Wifi module after the user's + // "connect" request, and the user still sees "not connected" message for a while, which + // looks strange for users though legitimate from the view of the module. + // // We instead manually show "connecting" message before the system gets actual - // "connecting" message from Wi-Fi module. - showConnectingStatus(); + // "connecting" message from Wifi module. + showConnectingState(); // Might be better to delay showing this button. mBackButton.setVisibility(View.VISIBLE); mBackButton.setText(R.string.wifi_setup_back); - // We need to restore visibility status when the device failed to connect the network. - final View wpsFieldView = findViewById(R.id.wps_fields); - if (wpsFieldView != null) { - mPreviousWpsFieldsVisibility = wpsFieldView.getVisibility(); - wpsFieldView.setVisibility(View.GONE); - } - final View securityFieldsView = findViewById(R.id.security_fields); - if (securityFieldsView != null) { - mPreviousSecurityFieldsVisibility = securityFieldsView.getVisibility(); - securityFieldsView.setVisibility(View.GONE); - } - final View typeView = findViewById(R.id.type); - if (typeView != null) { - mPreviousTypeVisibility = typeView.getVisibility(); - typeView.setVisibility(View.GONE); - } - - // TODO: investigate whether visibility handling above is needed. Now that we hide - // them completely when connecting, so we may not need to do so, though we probably - // need to show software keyboard conditionaly. final ViewGroup parent = (ViewGroup)findViewById(R.id.wifi_config_ui); parent.setVisibility(View.GONE); mConnectingStatusLayout.setVisibility(View.VISIBLE); mConnectingStatusView.setText(R.string.wifi_setup_description_connecting); - mHandler.removeCallbacks(mSkipButtonEnabler); mSkipOrNextButton.setVisibility(View.VISIBLE); mSkipOrNextButton.setEnabled(false); mConnectButton.setVisibility(View.GONE); @@ -498,29 +576,48 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis private void onBackButtonPressed() { trySetBackground(R.drawable.setups_bg_default); - if (mAfterConnectAction) { + if (mScreenState == SCREEN_STATE_CONNECTING || mScreenState == SCREEN_STATE_CONNECTED) { if (DEBUG) Log.d(TAG, "Back button pressed after connect action."); - mAfterConnectAction = false; + mScreenState = SCREEN_STATE_DISCONNECTED; // When a user press "Back" button after pressing "Connect" button, we want to cancel - // the "Connect" request and refresh the whole wifi status. - restoreFirstButtonVisibilityState(); + // the "Connect" request and refresh the whole Wifi status. + restoreFirstVisibilityState(); mSkipOrNextButton.setEnabled(true); changeNextButtonState(false); // Skip + // Wifi list becomes empty for a moment. We show "scanning" effect to a user so that + // he/she won't be astonished there. This stops once the scan finishes. + showScanningState(); + + // Remembered networks may be re-used during SetupWizard, which confuse users. + // We force the module to forget them to reduce UX complexity + final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + for (WifiConfiguration config : configs) { + if (DEBUG) { + Log.d(TAG, String.format("forgeting Wi-Fi network \"%s\" (id: %d)", + config.SSID, config.networkId)); + } + mWifiManager.forgetNetwork(config.networkId); + } + + mWifiSettingsFragmentLayout.setVisibility(View.GONE); refreshAccessPoints(true); } else { // During user's Wifi configuration. + mScreenState = SCREEN_STATE_DISCONNECTED; mWifiSettings.resumeWifiScan(); - restoreFirstButtonVisibilityState(); + restoreFirstVisibilityState(); mAddNetworkButton.setEnabled(true); mRefreshButton.setEnabled(true); mSkipOrNextButton.setEnabled(true); + mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); + showDisconnectedProgressBar(); } - findViewById(R.id.wifi_setup).setVisibility(View.VISIBLE); + setPaddingVisibility(View.VISIBLE); mConnectingStatusLayout.setVisibility(View.GONE); final ViewGroup parent = (ViewGroup)findViewById(R.id.wifi_config_ui); parent.removeAllViews(); @@ -548,7 +645,11 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis // If we already show some of access points but the bar still shows "scanning" state, it // should be stopped. if (mProgressBar.isIndeterminate() && accessPoints.size() > 0) { - mProgressBar.setIndeterminate(false); + showDisconnectedProgressBar(); + if (mScreenState == SCREEN_STATE_DISCONNECTED) { + mWifiSettingsFragmentLayout.setVisibility(View.VISIBLE); + mBottomPadding.setVisibility(View.GONE); + } mAddNetworkButton.setEnabled(true); mRefreshButton.setEnabled(true); } @@ -565,9 +666,9 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis ((Integer)tag == CONNECT_BUTTON_TAG_ADD_NETWORK)) { // In "Add network" flow, we won't get DetaledState available for changing ProgressBar // state. Instead we manually show previous status here. - showDisconnectedStatus(Summary.get(this, mPreviousState)); + showDisconnectedState(Summary.get(this, mPreviousNetworkState)); } else { - showScanningStatus(); + showScanningState(); } if (disconnectNetwork) { @@ -593,7 +694,8 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis * Called once when Authentication failed. */ private void onAuthenticationFailure() { - mAfterConnectAction = false; + mScreenState = SCREEN_STATE_EDITING; + mSkipOrNextButton.setVisibility(View.GONE); mConnectButton.setVisibility(View.VISIBLE); mConnectButton.setEnabled(true); @@ -610,56 +712,56 @@ public class WifiSettingsForSetupWizardXL extends Activity implements OnClickLis final ViewGroup parent = (ViewGroup)findViewById(R.id.wifi_config_ui); parent.setVisibility(View.VISIBLE); mConnectingStatusLayout.setVisibility(View.GONE); + } - // Restore View status which was tweaked on connection. - final View wpsFieldView = findViewById(R.id.wps_fields); - if (wpsFieldView != null) { - wpsFieldView.setVisibility(mPreviousWpsFieldsVisibility); - } - final View securityFieldsView = findViewById(R.id.security_fields); - if (securityFieldsView != null) { - securityFieldsView.setVisibility(mPreviousSecurityFieldsVisibility); - if (mPreviousSecurityFieldsVisibility == View.VISIBLE && mWifiConfig != null) { - final View passwordView = findViewById(R.id.password); - if (passwordView != null) { - if (passwordView.isFocused()) { - setPaddingVisibility(View.GONE); - } - mWifiConfig.requestFocusAndShowKeyboard(R.id.password); - } - } - } - final View typeView = findViewById(R.id.type); - if (typeView != null) { - typeView.setVisibility(mPreviousTypeVisibility); - if (mPreviousTypeVisibility == View.VISIBLE && mWifiConfig != null) { - final View ssidView = findViewById(R.id.ssid); - if (ssidView != null) { - if (ssidView.isFocused()) { - setPaddingVisibility(View.GONE); - } - mWifiConfig.requestFocusAndShowKeyboard(R.id.ssid); - } - } + // Used by WifiConfigUiForSetupWizardXL + /* package */ void setPaddingVisibility(int visibility) { + mTopPadding.setVisibility(visibility); + mContentPadding.setVisibility(visibility); + } + + private void showDisconnectedProgressBar() { + // The device may report DISCONNECTED during connecting to a network, at which we don't + // want to lose bottom padding of top divider implicitly added by ProgressBar. + if (mScreenState == SCREEN_STATE_DISCONNECTED) { + mProgressBar.setVisibility(View.GONE); + mProgressBar.setIndeterminate(false); + mTopDividerNoProgress.setVisibility(View.VISIBLE); + } else { + mProgressBar.setVisibility(View.VISIBLE); + mProgressBar.setIndeterminate(false); + mTopDividerNoProgress.setVisibility(View.GONE); } } - public void setPaddingVisibility(int visibility) { - setPaddingVisibility(visibility, visibility); + /** + * Shows top divider with ProgressBar, whose state is intermediate. + */ + private void showScanningProgressBar() { + showTopDividerWithProgressBar(); + mProgressBar.setIndeterminate(true); + } + + /** + * Shows top divider with ProgressBar, showing "connecting" state. + */ + private void showConnectingProgressBar() { + showTopDividerWithProgressBar(); + mProgressBar.setIndeterminate(false); + mProgressBar.setProgress(1); } - private void setPaddingVisibility(int topPaddingVisibility, int configVisibility) { - mTopPadding.setVisibility(topPaddingVisibility); - mWifiConfigPadding.setVisibility(configVisibility); + private void showConnectedProgressBar() { + showTopDividerWithProgressBar(); + mProgressBar.setIndeterminate(false); + mProgressBar.setProgress(2); } /** - * Called when WifiManager is requested to save a network. This method sholud include - * WifiManager#saveNetwork() call. - * - * Currently this method calls {@link WifiManager#connectNetwork(int)}. + * Called when WifiManager is requested to save a network. */ /* package */ void onSaveNetwork(WifiConfiguration config) { + // We want to both save and connect a network. connectNetwork() does both. mWifiManager.connectNetwork(config); } |