From df9504ef58e8dafdd80ca9cd780510f9444943e2 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Sun, 6 Dec 2009 01:04:04 -0800 Subject: b/2296110 Dialog for setting up dock audio. Reimplemented as a Foreground Service just so it can get on top of the Car Dock App. Added debounce mechanism to not disconnect immediately after getting a undock event. Each dock now has its own "Remember setting". Remember Settings is on by default Change-Id: I80790bdb7c831e2a642365b92433012191aa70c1 --- AndroidManifest.xml | 10 +- res/values/strings.xml | 2 - .../android/settings/SoundAndDisplaySettings.java | 47 +- .../settings/bluetooth/CachedBluetoothDevice.java | 64 +-- .../bluetooth/DockAudioStateChangeReceiver.java | 76 ---- .../settings/bluetooth/DockEventReceiver.java | 112 +++++ .../android/settings/bluetooth/DockService.java | 495 +++++++++++++++++++++ .../settings/bluetooth/DockSettingsActivity.java | 312 ------------- 8 files changed, 666 insertions(+), 452 deletions(-) delete mode 100644 src/com/android/settings/bluetooth/DockAudioStateChangeReceiver.java create mode 100644 src/com/android/settings/bluetooth/DockEventReceiver.java create mode 100644 src/com/android/settings/bluetooth/DockService.java delete mode 100644 src/com/android/settings/bluetooth/DockSettingsActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 974950d..f3f33ac 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -489,20 +489,14 @@ + android:name=".bluetooth.DockEventReceiver"> - - + For music and media Remember settings - - "Change with Settings under Sound & display > Dock audio diff --git a/src/com/android/settings/SoundAndDisplaySettings.java b/src/com/android/settings/SoundAndDisplaySettings.java index edcd4da..e01f7c3 100644 --- a/src/com/android/settings/SoundAndDisplaySettings.java +++ b/src/com/android/settings/SoundAndDisplaySettings.java @@ -18,7 +18,7 @@ package com.android.settings; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; -import com.android.settings.bluetooth.DockSettingsActivity; +import com.android.settings.bluetooth.DockEventReceiver; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -49,7 +49,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements /** If there is no setting in the provider, use this. */ private static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000; private static final int FALLBACK_EMERGENCY_TONE_VALUE = 0; - + private static final String KEY_SILENT = "silent"; private static final String KEY_VIBRATE = "vibrate"; private static final String KEY_SCREEN_TIMEOUT = "screen_timeout"; @@ -89,9 +89,9 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements private CheckBoxPreference mAccelerometer; private CheckBoxPreference mNotificationPulse; private float[] mAnimationScales; - + private AudioManager mAudioManager; - + private IWindowManager mWindowManager; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -114,12 +114,12 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements super.onCreate(savedInstanceState); ContentResolver resolver = getContentResolver(); int activePhoneType = TelephonyManager.getDefault().getPhoneType(); - + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); - + addPreferencesFromResource(R.xml.sound_and_display_settings); if (TelephonyManager.PHONE_TYPE_CDMA != activePhoneType) { @@ -147,7 +147,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements mAnimations.setOnPreferenceChangeListener(this); mAccelerometer = (CheckBoxPreference) findPreference(KEY_ACCELEROMETER); mAccelerometer.setPersistent(false); - + ListPreference screenTimeoutPreference = (ListPreference) findPreference(KEY_SCREEN_TIMEOUT); screenTimeoutPreference.setValue(String.valueOf(Settings.System.getInt( @@ -184,9 +184,9 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements @Override protected void onResume() { super.onResume(); - + updateState(true); - + IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); if (mHasDockSettings) { if (mDockSettings != null) { @@ -236,7 +236,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements final int ringerMode = mAudioManager.getRingerMode(); final boolean silentOrVibrateMode = ringerMode != AudioManager.RINGER_MODE_NORMAL; - + if (silentOrVibrateMode != mSilent.isChecked() || force) { mSilent.setChecked(silentOrVibrateMode); } @@ -245,25 +245,25 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements mPlayMediaNotificationSounds.setChecked(mMountService.getPlayNotificationSounds()); } catch (RemoteException e) { } - + boolean vibrateSetting; if (silentOrVibrateMode) { vibrateSetting = ringerMode == AudioManager.RINGER_MODE_VIBRATE; } else { vibrateSetting = mAudioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER) - == AudioManager.VIBRATE_SETTING_ON; + == AudioManager.VIBRATE_SETTING_ON; } if (vibrateSetting != mVibrate.isChecked() || force) { mVibrate.setChecked(vibrateSetting); } - + int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - boolean isAlarmInclSilentMode = (silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0; + boolean isAlarmInclSilentMode = (silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0; mSilent.setSummary(isAlarmInclSilentMode ? R.string.silent_mode_incl_alarm_summary : R.string.silent_mode_summary); - + int animations = 0; try { mAnimationScales = mWindowManager.getAnimationScales(); @@ -290,7 +290,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements mAnimations.setValueIndex(idx); updateAnimationsSummary(mAnimations.getValue()); mAccelerometer.setChecked(Settings.System.getInt( - getContentResolver(), + getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) != 0); } @@ -306,7 +306,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements } } } - + private void setRingerMode(boolean silent, boolean vibrate) { if (silent) { mAudioManager.setRingerMode(vibrate ? AudioManager.RINGER_MODE_VIBRATE : @@ -332,7 +332,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements } else if (preference == mDtmfTone) { Settings.System.putInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, mDtmfTone.isChecked() ? 1 : 0); - + } else if (preference == mSoundEffects) { if (mSoundEffects.isChecked()) { mAudioManager.loadSoundEffects(); @@ -345,7 +345,7 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements } else if (preference == mHapticFeedback) { Settings.System.putInt(getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, mHapticFeedback.isChecked() ? 1 : 0); - + } else if (preference == mAccelerometer) { Settings.System.putInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, @@ -356,8 +356,9 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements Settings.System.NOTIFICATION_LIGHT_PULSE, value ? 1 : 0); } else if (preference == mDockSettings) { Intent i = new Intent(mDockIntent); - i.setClass(this, DockSettingsActivity.class); - startActivity(i); + i.setAction(DockEventReceiver.ACTION_DOCK_SHOW_UI); + i.setClass(this, DockEventReceiver.class); + sendBroadcast(i); } return true; @@ -382,12 +383,12 @@ public class SoundAndDisplaySettings extends PreferenceActivity implements } catch (NumberFormatException e) { Log.e(TAG, "could not persist animation setting", e); } - + } if (KEY_SCREEN_TIMEOUT.equals(key)) { int value = Integer.parseInt((String) objValue); try { - Settings.System.putInt(getContentResolver(), + Settings.System.putInt(getContentResolver(), SCREEN_OFF_TIMEOUT, value); } catch (NumberFormatException e) { Log.e(TAG, "could not persist screen timeout setting", e); diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java index f749cf7..c3b97f6 100644 --- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java +++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java @@ -227,44 +227,46 @@ public class CachedBluetoothDevice implements Comparable } public void onProfileStateChanged(Profile profile, int newProfileState) { - if (D) { - Log.d(TAG, "onProfileStateChanged:" + workQueue.toString()); - } - - int newState = LocalBluetoothProfileManager.getProfileManager(mLocalManager, - profile).convertState(newProfileState); - - if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED) { - if (!mProfiles.contains(profile)) { - mProfiles.add(profile); + synchronized (workQueue) { + if (D) { + Log.d(TAG, "onProfileStateChanged:" + workQueue.toString()); } - } - /* Ignore the transient states e.g. connecting, disconnecting */ - if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED || - newState == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) { - BluetoothJob job = workQueue.peek(); - if (job == null) { - return; - } else if (!job.cachedDevice.mDevice.equals(mDevice)) { - // This can happen in 2 cases: 1) BT device initiated pairing and - // 2) disconnects of one headset that's triggered by connects of - // another. - if (D) { - Log.d(TAG, "mDevice:" + mDevice + " != head:" + job.toString()); + int newState = LocalBluetoothProfileManager.getProfileManager(mLocalManager, + profile).convertState(newProfileState); + + if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED) { + if (!mProfiles.contains(profile)) { + mProfiles.add(profile); } + } - // Check to see if we need to remove the stale items from the queue - if (!pruneQueue(null)) { - // nothing in the queue was modify. Just ignore the notification and return. + /* Ignore the transient states e.g. connecting, disconnecting */ + if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED || + newState == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) { + BluetoothJob job = workQueue.peek(); + if (job == null) { return; + } else if (!job.cachedDevice.mDevice.equals(mDevice)) { + // This can happen in 2 cases: 1) BT device initiated pairing and + // 2) disconnects of one headset that's triggered by connects of + // another. + if (D) { + Log.d(TAG, "mDevice:" + mDevice + " != head:" + job.toString()); + } + + // Check to see if we need to remove the stale items from the queue + if (!pruneQueue(null)) { + // nothing in the queue was modify. Just ignore the notification and return. + return; + } + } else { + // Remove the first item and process the next one + workQueue.poll(); } - } else { - // Remove the first item and process the next one - workQueue.poll(); - } - processCommands(); + processCommands(); + } } } diff --git a/src/com/android/settings/bluetooth/DockAudioStateChangeReceiver.java b/src/com/android/settings/bluetooth/DockAudioStateChangeReceiver.java deleted file mode 100644 index d320742..0000000 --- a/src/com/android/settings/bluetooth/DockAudioStateChangeReceiver.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.bluetooth; - -import android.bluetooth.BluetoothDevice; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -public class DockAudioStateChangeReceiver extends BroadcastReceiver { - - private static final boolean DBG = true; - private static final String TAG = "DockAudioStateChangeReceiver"; - - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null) - return; - - if (DBG) { - Log.e(TAG, "Action:" + intent.getAction() - + " State:" + intent.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED)); - } - - if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device == null) { - if (DBG) Log.e(TAG, "Device is missing"); - return; - } - - LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(context); - - int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - - switch (state) { - case Intent.EXTRA_DOCK_STATE_UNDOCKED: - DockSettingsActivity.handleUndocked(context, localManager, device); - break; - case Intent.EXTRA_DOCK_STATE_CAR: - case Intent.EXTRA_DOCK_STATE_DESK: - if (DockSettingsActivity.getAutoConnectSetting(localManager)) { - // Auto connect - DockSettingsActivity.handleDocked(context, localManager, device, state); - } else { - // Don't auto connect. Show dialog. - Intent i = new Intent(intent); - i.setClass(context, DockSettingsActivity.class); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - break; - default: - Log.e(TAG, "Unknown state"); - break; - } - } - } -} diff --git a/src/com/android/settings/bluetooth/DockEventReceiver.java b/src/com/android/settings/bluetooth/DockEventReceiver.java new file mode 100644 index 0000000..a2678b9 --- /dev/null +++ b/src/com/android/settings/bluetooth/DockEventReceiver.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.app.Service; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.PowerManager; +import android.util.Log; + +public class DockEventReceiver extends BroadcastReceiver { + + private static final boolean DEBUG = true; + + private static final String TAG = "DockEventReceiver"; + + public static final String ACTION_DOCK_SHOW_UI = + "com.android.settings.bluetooth.action.DOCK_SHOW_UI"; + + private static final int EXTRA_INVALID = -1234; + + static final Object mStartingServiceSync = new Object(); + + static PowerManager.WakeLock mStartingService; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) + return; + + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, EXTRA_INVALID); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + if (DEBUG) { + Log.d(TAG, "Action: " + intent.getAction() + " State:" + state + " Device: " + + (device == null ? "null" : device.getName())); + } + + if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction()) + || ACTION_DOCK_SHOW_UI.endsWith(intent.getAction())) { + if (device == null) { + if (DEBUG) Log.e(TAG, "Device is missing"); + return; + } + + switch (state) { + case Intent.EXTRA_DOCK_STATE_UNDOCKED: + case Intent.EXTRA_DOCK_STATE_CAR: + case Intent.EXTRA_DOCK_STATE_DESK: + Intent i = new Intent(intent); + i.setClass(context, DockService.class); + beginStartingService(context, i); + break; + default: + if (DEBUG) Log.e(TAG, "Unknown state"); + break; + } + } + } + + /** + * 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) { + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "StartingDockService"); + mStartingService.setReferenceCounted(false); + } + + mStartingService.acquire(); + + if (context.startService(intent) == null) { + Log.e(TAG, "Can't start DockService"); + mStartingService.release(); + } + } + } + + /** + * Called back by the service when it has finished processing notifications, + * releasing the wake lock if the service is now stopping. + */ + public static void finishStartingService(Service service, int startId) { + synchronized (mStartingServiceSync) { + if (mStartingService != null) { + if (service.stopSelfResult(startId)) { + mStartingService.release(); + } + } + } + } +} diff --git a/src/com/android/settings/bluetooth/DockService.java b/src/com/android/settings/bluetooth/DockService.java new file mode 100644 index 0000000..245b10d --- /dev/null +++ b/src/com/android/settings/bluetooth/DockService.java @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import com.android.settings.R; +import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; + +import android.app.AlertDialog; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.CompoundButton; + +public class DockService extends Service implements AlertDialog.OnMultiChoiceClickListener, + DialogInterface.OnClickListener, DialogInterface.OnDismissListener, + CompoundButton.OnCheckedChangeListener { + + // TODO check for waitlock leak + // TODO check for service shutting down properly + // TODO sticky vs non-sticky + // TODO clean up static functions + // TODO test after wiping data + + private static final String TAG = "DockService"; + + // TODO clean up logs. Disable DEBUG flag for this file and receiver's too + private static final boolean DEBUG = true; + + private static final String SHARED_PREFERENCE_KEY_AUTO_CONNECT_TO_DOCK = "auto_connect_to_dock"; + + // Time allowed for the device to be undocked and redocked without severing + // the bluetooth connection + private static final long UNDOCKED_GRACE_PERIOD = 1000; + + // Msg for user wanting the UI to setup the dock + private static final int MSG_TYPE_SHOW_UI = 111; + // Msg for device docked event + private static final int MSG_TYPE_DOCKED = 222; + // Msg for device undocked event + private static final int MSG_TYPE_UNDOCKED_TEMPORARY = 333; + // Msg for undocked command to be process after UNDOCKED_GRACE_PERIOD millis + // since MSG_TYPE_UNDOCKED_TEMPORARY + private static final int MSG_TYPE_UNDOCKED_PERMANENT = 444; + + // Created in OnCreate() + private volatile Looper mServiceLooper; + private volatile ServiceHandler mServiceHandler; + private DockService mContext; + private LocalBluetoothManager mBtManager; + + // 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 + // was started after the docked event. + private BluetoothDevice mDevice; + + // Created and used for the duration of the dialog + private AlertDialog mDialog; + private Profile[] mProfiles; + private boolean[] mCheckedItems; + private int mStartIdAssociatedWithDialog; + + @Override + public void onCreate() { + if (DEBUG) Log.d(TAG, "onCreate"); + + mBtManager = LocalBluetoothManager.getInstance(this); + mContext = this; + + HandlerThread thread = new HandlerThread("DockService"); + thread.start(); + + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } + + @Override + public void onDestroy() { + if (DEBUG) Log.d(TAG, "onDestroy"); + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + mServiceLooper.quit(); + } + + @Override + public IBinder onBind(Intent intent) { + // not supported + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (DEBUG) Log.d(TAG, "onStartCommand startId:" + startId + " flags: " + flags); + + if (intent == null) { + // Nothing to process, stop. + if (DEBUG) Log.d(TAG, "START_NOT_STICKY - intent is null."); + + // NOTE: We MUST not call stopSelf() directly, since we need to + // make sure the wake lock acquired by the Receiver is released. + DockEventReceiver.finishStartingService(this, startId); + return START_NOT_STICKY; + } + + Message msg = parseIntent(intent); + if (msg == null) { + // Bad intent + if (DEBUG) Log.d(TAG, "START_NOT_STICKY - Bad intent."); + DockEventReceiver.finishStartingService(this, startId); + return START_NOT_STICKY; + } + + msg.arg2 = startId; + processMessage(msg); + + return START_STICKY; + } + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + processMessage(msg); + } + } + + // This method gets messages from both onStartCommand and mServiceHandler/mServiceLooper + void processMessage(Message msg) { + int msgType = msg.what; + int state = msg.arg1; + int startId = msg.arg2; + BluetoothDevice device = (BluetoothDevice) msg.obj; + + if(DEBUG) Log.d(TAG, "processMessage: " + msgType + " state: " + state + " device = " + + (msg.obj == null ? "null" : device.toString())); + + switch (msgType) { + case MSG_TYPE_SHOW_UI: + //TODO dismiss mDialog if exist? Shouldn't normally happen + mDevice = device; + createDialog(mContext, mDevice, 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); + + if (!device.equals(mDevice)) { + if (mDevice != null) { + // Not expected. Cleanup/undock existing + handleUndocked(mContext, mBtManager, mDevice); + } + + mDevice = device; + if (getAutoConnectSetting(mBtManager, device.getAddress())) { + // Setting == auto connect + initBtSettings(mContext, device, state, false); + applyBtSettings(); + } else { + createDialog(mContext, mDevice, state, startId); + } + } + break; + + case MSG_TYPE_UNDOCKED_PERMANENT: + // Grace period passed. Disconnect. + handleUndocked(mContext, mBtManager, device); + 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, + 0, device); + mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD); + break; + } + + if (mDialog == null) { + // 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, msg.arg1); + } + } + + private Message parseIntent(Intent intent) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1234); + + if (DEBUG) { + Log.d(TAG, "Action: " + intent.getAction() + " State:" + state + + " Device: " + (device == null ? "null" : device.getName())); + } + + if (device == null) { + Log.e(TAG, "device is null"); + return null; + } + + int msgType; + switch (state) { + case Intent.EXTRA_DOCK_STATE_UNDOCKED: + msgType = MSG_TYPE_UNDOCKED_TEMPORARY; + break; + case Intent.EXTRA_DOCK_STATE_DESK: + case Intent.EXTRA_DOCK_STATE_CAR: + if (DockEventReceiver.ACTION_DOCK_SHOW_UI.equals(intent.getAction())) { + msgType = MSG_TYPE_SHOW_UI; + } else { + msgType = MSG_TYPE_DOCKED; + } + break; + default: + return null; + } + + return mServiceHandler.obtainMessage(msgType, state, 0, device); + } + + private boolean createDialog(DockService service, BluetoothDevice device, int state, + int startId) { + switch (state) { + case Intent.EXTRA_DOCK_STATE_CAR: + case Intent.EXTRA_DOCK_STATE_DESK: + break; + default: + return false; + } + + startForeground(0, new Notification()); + + // Device in a new dock. + boolean firstTime = !hasAutoConnectSetting(mBtManager, device.getAddress()); + + CharSequence[] items = initBtSettings(service, device, state, firstTime); + + final AlertDialog.Builder ab = new AlertDialog.Builder(service); + ab.setTitle(service.getString(R.string.bluetooth_dock_settings_title)); + + // Profiles + ab.setMultiChoiceItems(items, mCheckedItems, service); + + // Remember this settings + LayoutInflater inflater = (LayoutInflater) service + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + float pixelScaleFactor = service.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 || getAutoConnectSetting(mBtManager, device.getAddress()); + rememberCheckbox.setChecked(checked); + rememberCheckbox.setOnCheckedChangeListener(this); + 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 = " + getAutoConnectSetting(mBtManager, device.getAddress())); + } + + // Ok Button + ab.setPositiveButton(service.getString(android.R.string.ok), service); + + mStartIdAssociatedWithDialog = startId; + mDialog = ab.create(); + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + mDialog.setOnDismissListener(service); + 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; + } + + // Called when the "Remember" Checkbox is clicked + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (DEBUG) Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked); + saveAutoConnectSetting(mBtManager, 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. + DockEventReceiver.finishStartingService(mContext, mStartIdAssociatedWithDialog); + mContext.stopForeground(true); + } + + // Called when clicked on the OK button + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + if (!hasAutoConnectSetting(mBtManager, mDevice.getAddress())) { + saveAutoConnectSetting(mBtManager, mDevice.getAddress(), true); + } + + // TODO move this to a background thread + switch (mBtManager.getBluetoothState()) { + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + mBtManager.getBluetoothAdapter().enable(); + // TODO can I call connect right away? probably not. + break; + case BluetoothAdapter.STATE_TURNING_ON: + // TODO wait? probably + break; + case BluetoothAdapter.STATE_ON: + break; + } + + applyBtSettings(); + } + } + + private CharSequence[] initBtSettings(DockService service, BluetoothDevice device, int state, + boolean firstTime) { + // TODO Avoid hardcoding dock and profiles. Read from system properties + int numOfProfiles = 0; + switch (state) { + case Intent.EXTRA_DOCK_STATE_DESK: + numOfProfiles = 1; + break; + case Intent.EXTRA_DOCK_STATE_CAR: + numOfProfiles = 2; + break; + default: + return null; + } + + mProfiles = new Profile[numOfProfiles]; + mCheckedItems = new boolean[numOfProfiles]; + CharSequence[] items = new CharSequence[numOfProfiles]; + + int i = 0; + switch (state) { + case Intent.EXTRA_DOCK_STATE_CAR: + items[i] = service.getString(R.string.bluetooth_dock_settings_headset); + mProfiles[i] = Profile.HEADSET; + if (firstTime) { + mCheckedItems[i] = true; + } else { + mCheckedItems[i] = LocalBluetoothProfileManager.getProfileManager(mBtManager, + Profile.HEADSET).isPreferred(device); + } + ++i; + // fall through + case Intent.EXTRA_DOCK_STATE_DESK: + items[i] = service.getString(R.string.bluetooth_dock_settings_a2dp); + mProfiles[i] = Profile.A2DP; + if (firstTime) { + mCheckedItems[i] = true; + } else { + mCheckedItems[i] = LocalBluetoothProfileManager.getProfileManager(mBtManager, + Profile.A2DP).isPreferred(device); + } + break; + } + return items; + } + + private void applyBtSettings() { + if (mProfiles == null) return; + for (int i = 0; i < mProfiles.length; i++) { + LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager + .getProfileManager(mBtManager, mProfiles[i]); + boolean isConnected = profileManager.isConnected(mDevice); + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager, + mDevice); + + if (DEBUG) Log.d(TAG, mProfiles[i].toString() + " = " + mCheckedItems[i]); + + if (mCheckedItems[i] && !isConnected) { + // Checked but not connected + if (DEBUG) Log.d(TAG, "Connecting "); + cachedDevice.connect(mProfiles[i]); + } else if (!mCheckedItems[i] && isConnected) { + // Unchecked but connected + if (DEBUG) Log.d(TAG, "Disconnecting"); + cachedDevice.disconnect(mProfiles[i]); + } + profileManager.setPreferred(mDevice, mCheckedItems[i]); + if (DEBUG) { + if (mCheckedItems[i] != LocalBluetoothProfileManager.getProfileManager( + mBtManager, Profile.HEADSET).isPreferred(mDevice)) { + Log.e(TAG, "Can't save prefered value"); + } + } + } + } + + void handleUndocked(Context context, LocalBluetoothManager localManager, + BluetoothDevice device) { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + mDevice = null; + CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context, + localManager, device); + cachedBluetoothDevice.disconnect(); + } + + void handleDocked(Context context, LocalBluetoothManager localManager, + BluetoothDevice device, int state) { + CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context, + localManager, device); + cachedBluetoothDevice.connect(); + } + + 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); + } + return cachedBluetoothDevice; + } + + public static boolean hasAutoConnectSetting(LocalBluetoothManager localManager, String addr) { + return localManager.getSharedPreferences().contains( + SHARED_PREFERENCE_KEY_AUTO_CONNECT_TO_DOCK + addr); + } + + public static boolean getAutoConnectSetting(LocalBluetoothManager localManager, String addr) { + return localManager.getSharedPreferences().getBoolean( + SHARED_PREFERENCE_KEY_AUTO_CONNECT_TO_DOCK + addr, false); + } + + public static void saveAutoConnectSetting(LocalBluetoothManager localManager, String addr, + boolean autoConnect) { + SharedPreferences.Editor editor = localManager.getSharedPreferences().edit(); + editor.putBoolean(SHARED_PREFERENCE_KEY_AUTO_CONNECT_TO_DOCK + addr, autoConnect); + editor.commit(); + } + + // TODO Delete this method if not needed. + private Notification getNotification(Service service) { + CharSequence title = service.getString(R.string.dock_settings_title); + + Notification n = new Notification(R.drawable.ic_bt_headphones_a2dp, title, System + .currentTimeMillis()); + + CharSequence contentText = service.getString(R.string.dock_settings_summary); + Intent notificationIntent = new Intent(service, DockEventReceiver.class); + notificationIntent.setAction(DockEventReceiver.ACTION_DOCK_SHOW_UI); + PendingIntent pendingIntent = PendingIntent.getActivity(service, 0, notificationIntent, 0); + + n.setLatestEventInfo(service, title, contentText, pendingIntent); + return n; + } +} diff --git a/src/com/android/settings/bluetooth/DockSettingsActivity.java b/src/com/android/settings/bluetooth/DockSettingsActivity.java deleted file mode 100644 index f5e0055..0000000 --- a/src/com/android/settings/bluetooth/DockSettingsActivity.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.bluetooth; - -import com.android.internal.app.AlertActivity; -import com.android.internal.app.AlertController; -import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - -import android.app.AlertDialog; -import android.app.Notification; -import android.app.NotificationManager; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.WindowManager; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; - -/** - * RequestPermissionActivity asks the user whether to enable discovery. This is - * usually started by an application wanted to start bluetooth and or discovery - */ -public class DockSettingsActivity extends AlertActivity implements DialogInterface.OnClickListener, - AlertDialog.OnMultiChoiceClickListener, OnCheckedChangeListener { - - private static final String TAG = "DockSettingsActivity"; - - private static final boolean DEBUG = true; - - private static final String SHARED_PREFERENCES_KEY_AUTO_CONNECT_TO_DOCK = "auto_connect_to_dock"; - - private BluetoothDevice mDevice; - - private int mState = Intent.EXTRA_DOCK_STATE_UNDOCKED; - - private CachedBluetoothDevice mCachedDevice; - - private LocalBluetoothManager mLocalManager; - - private LocalBluetoothProfileManager mA2dpMgr; - - private LocalBluetoothProfileManager mHeadsetMgr; - - private LocalBluetoothProfileManager[] mProfileManagers; - - private boolean[] mCheckedItems; - - private CheckBox mRememberCheck; - - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (!parseIntent(intent)) { - finish(); - return; - } - - if (DEBUG) Log.d(TAG, "Action: " + intent.getAction() + " State: " + mState); - } - }; - - private Profile[] mProfiles; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (!parseIntent(getIntent())) { - finish(); - return; - } - - if (mState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - handleUndocked(this, mLocalManager, mDevice); - dismiss(); - return; - } - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); - createDialog(); - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); - } - - @Override - protected void onNewIntent(Intent intent) { - if (!parseIntent(getIntent())) { - finish(); - return; - } - } - - @Override - protected void onResume() { - super.onResume(); - - IntentFilter filter = new IntentFilter(Intent.ACTION_DOCK_EVENT); - registerReceiver(mReceiver, filter); - } - - @Override - protected void onPause() { - super.onPause(); - - unregisterReceiver(mReceiver); - } - - private void createDialog() { - // TODO Avoid hardcoding dock and profiles. Read from system properties - int numOfProfiles; - switch (mState) { - case Intent.EXTRA_DOCK_STATE_CAR: - numOfProfiles = 2; - break; - case Intent.EXTRA_DOCK_STATE_DESK: - numOfProfiles = 1; - break; - default: - return; - } - - CharSequence[] items = new CharSequence[numOfProfiles]; - mCheckedItems = new boolean[numOfProfiles]; - mProfileManagers = new LocalBluetoothProfileManager[numOfProfiles]; - mProfiles = new Profile[numOfProfiles]; - - int i = 0; - switch (mState) { - case Intent.EXTRA_DOCK_STATE_CAR: - mProfileManagers[i] = mHeadsetMgr; - mProfiles[i] = Profile.HEADSET; - mCheckedItems[i] = mHeadsetMgr.isPreferred(mDevice); - items[i] = getString(R.string.bluetooth_dock_settings_headset); - ++i; - // fall through - case Intent.EXTRA_DOCK_STATE_DESK: - mProfileManagers[i] = mA2dpMgr; - mProfiles[i] = Profile.A2DP; - mCheckedItems[i] = mA2dpMgr.isPreferred(mDevice); - items[i] = getString(R.string.bluetooth_dock_settings_a2dp); - break; - } - - final AlertController.AlertParams p = mAlertParams; - p.mTitle = getString(R.string.bluetooth_dock_settings_title); - - // Profiles - p.mIsMultiChoice = true; - p.mItems = items; - p.mCheckedItems = mCheckedItems; - p.mOnCheckboxClickListener = this; - - // Remember this settings - LayoutInflater inflater = (LayoutInflater) getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - p.mView = inflater.inflate(R.layout.remember_dock_setting, null); - p.mViewSpacingSpecified = true; - float pixelScaleFactor = getResources().getDisplayMetrics().density; - p.mViewSpacingLeft = (int) (14 * pixelScaleFactor); - p.mViewSpacingRight = (int) (14 * pixelScaleFactor); - mRememberCheck = (CheckBox)p.mView.findViewById(R.id.remember); - if (DEBUG) Log.d(TAG, "Auto Check? = " + getAutoConnectSetting(mLocalManager)); - mRememberCheck.setChecked(getAutoConnectSetting(mLocalManager)); - mRememberCheck.setOnCheckedChangeListener(this); - - // Ok Button - p.mPositiveButtonText = getString(android.R.string.ok); - p.mPositiveButtonListener = this; - - setupAlert(); - } - - // Called when the individual items are clicked. - 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); - saveAutoConnectSetting(mLocalManager, isChecked); - } - - // Called when clicked on the OK button - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - switch (mLocalManager.getBluetoothState()) { - case BluetoothAdapter.STATE_OFF: - case BluetoothAdapter.STATE_TURNING_OFF: - mLocalManager.getBluetoothAdapter().enable(); - // TODO can I call connect right away? probably not. - break; - case BluetoothAdapter.STATE_TURNING_ON: - // TODO wait? probably - break; - case BluetoothAdapter.STATE_ON: - break; - } - - for(int i = 0; i < mProfileManagers.length; i++) { - mProfileManagers[i].setPreferred(mDevice, mCheckedItems[i]); - - if (DEBUG) Log.d(TAG, mProfileManagers[i].toString() + " = " + mCheckedItems[i]); - boolean isConnected = mProfileManagers[i].isConnected(mDevice); - if (mCheckedItems[i] && !isConnected) { - if (DEBUG) Log.d(TAG, "Connecting "); - mCachedDevice.connect(mProfiles[i]); - } else if (isConnected){ - if (DEBUG) Log.d(TAG, "Disconnecting"); - mProfileManagers[i].disconnect(mDevice); - } - } - } - } - - private boolean parseIntent(Intent intent) { - if (intent == null) { - return false; - } - - mLocalManager = LocalBluetoothManager.getInstance(this); - if (mLocalManager == null) { - if (DEBUG) Log.d(TAG, "Error: there's a problem starting bluetooth"); - return false; - } - - mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (mDevice == null) { - if (DEBUG) Log.d(TAG, "device == null"); - return false; - } - - mState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (mState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - handleUndocked(this, mLocalManager, mDevice); - return false; - } - - mCachedDevice = getCachedBluetoothDevice(this, mLocalManager, mDevice); - mA2dpMgr = LocalBluetoothProfileManager.getProfileManager(mLocalManager, Profile.A2DP); - mHeadsetMgr = LocalBluetoothProfileManager.getProfileManager(mLocalManager, - Profile.HEADSET); - - return true; - } - - public static void handleUndocked(Context context, LocalBluetoothManager localManager, - BluetoothDevice device) { - CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context, - localManager, device); - cachedBluetoothDevice.disconnect(); - } - - public static void handleDocked(Context context, LocalBluetoothManager localManager, - BluetoothDevice device, int state) { - CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context, - localManager, device); - cachedBluetoothDevice.connect(); - } - - 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); - } - return cachedBluetoothDevice; - } - - public static boolean hasAutoConnectSetting(LocalBluetoothManager localManager) { - return localManager.getSharedPreferences().contains( - SHARED_PREFERENCES_KEY_AUTO_CONNECT_TO_DOCK); - } - - public static boolean getAutoConnectSetting(LocalBluetoothManager localManager) { - return localManager.getSharedPreferences().getBoolean( - SHARED_PREFERENCES_KEY_AUTO_CONNECT_TO_DOCK, false); - } - - public static void saveAutoConnectSetting(LocalBluetoothManager localManager, - boolean autoConnect) { - SharedPreferences.Editor editor = localManager.getSharedPreferences().edit(); - editor.putBoolean(SHARED_PREFERENCES_KEY_AUTO_CONNECT_TO_DOCK, autoConnect); - editor.commit(); - } -} -- cgit v1.1