diff options
author | Matthew Xie <mattx@google.com> | 2011-07-25 11:14:25 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-07-25 11:14:25 -0700 |
commit | 184dd03c81e924f610cf88dae266f2e5629b114d (patch) | |
tree | 60cfefa6fa714118c14e306547a0ad20a64ed464 | |
parent | d92eb588974556eff387dceecbaa36c3d3c69c5f (diff) | |
parent | 7f9ecca8f2dc288f785b37d2478e89b80fc3cefc (diff) | |
download | frameworks_base-184dd03c81e924f610cf88dae266f2e5629b114d.zip frameworks_base-184dd03c81e924f610cf88dae266f2e5629b114d.tar.gz frameworks_base-184dd03c81e924f610cf88dae266f2e5629b114d.tar.bz2 |
Merge "Keep Bluetooth module hot to quickly swith it on/off"
-rw-r--r-- | core/java/android/server/BluetoothAdapterStateMachine.java | 513 | ||||
-rw-r--r-- | core/java/android/server/BluetoothEventLoop.java | 18 | ||||
-rwxr-xr-x | core/java/android/server/BluetoothService.java | 304 | ||||
-rwxr-xr-x | core/res/res/values/config.xml | 4 |
4 files changed, 646 insertions, 193 deletions
diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java new file mode 100644 index 0000000..ae91465 --- /dev/null +++ b/core/java/android/server/BluetoothAdapterStateMachine.java @@ -0,0 +1,513 @@ +/* + * 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 android.server; + +import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Message; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.util.IState; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.PrintWriter; + +/** + * Bluetooth Adapter StateMachine + * All the states are at the same level, ie, no hierarchy. + * (BluetootOn) + * | ^ + * TURN_OFF | | BECOME_PAIRABLE + * AIRPLANE_MODE_ON | | + * V | + * (Switching) + * | ^ + * BECOME_NON_PAIRABLE& | | TURN_ON(_CONTINUE)/TURN_ON_FOR_PRIVILEGED + * ALL_DEVICES_DISCONNECTED | | + * V | + * (HotOff) + * / ^ + * / | SERVICE_RECORD_LOADED + * | | + * TURN_COLD | (Warmup) + * \ ^ + * \ | TURN_HOT/TURN_ON + * | | AIRPLANE_MODE_OFF(when Bluetooth was on before) + * V | + * (PowerOff) <----- initial state + * + */ +final class BluetoothAdapterStateMachine extends StateMachine { + private static final String TAG = "BluetoothAdapterStateMachine"; + private static final boolean DBG = false; + + // Message(what) to take an action + // + // We get this message when user tries to turn on BT + public static final int USER_TURN_ON = 1; + // We get this message when user tries to turn off BT + public static final int USER_TURN_OFF = 2; + + // Message(what) to report a event that the state machine need to respond to + // + // Event indicates sevice records have been loaded + public static final int SERVICE_RECORD_LOADED = 51; + // Event indicates all the remote Bluetooth devices has been disconnected + public static final int ALL_DEVICES_DISCONNECTED = 52; + // Event indicates the Bluetooth is connectable + public static final int BECOME_PAIRABLE = 53; + // Event indicates the Bluetooth is non-connectable. + public static final int BECOME_NON_PAIRABLE = 54; + // Event indicates airplane mode is turned on + public static final int AIRPLANE_MODE_ON = 55; + // Event indicates airplane mode is turned off + public static final int AIRPLANE_MODE_OFF = 56; + + // private internal messages + // + // Turn on Bluetooth Module, Load firmware, and do all the preparation + // needed to get the Bluetooth Module ready but keep it not discoverable + // and not connectable. This way the Bluetooth Module can be quickly + // switched on if needed + private static final int TURN_HOT = 101; + // USER_TURN_ON is changed to TURN_ON_CONTINUE after we broadcast the + // state change intent so that we will not broadcast the intent again in + // other state + private static final int TURN_ON_CONTINUE = 102; + // Unload firmware, turning off Bluetooth module power + private static final int TURN_COLD = 103; + // For NFC, turn on bluetooth for certain process + private static final int TURN_ON_FOR_PRIVILEGED = 104; + + private Context mContext; + private BluetoothService mBluetoothService; + private BluetoothEventLoop mEventLoop; + + private BluetoothOn mBluetoothOn; + private Switching mSwitching; + private HotOff mHotOff; + private WarmUp mWarmUp; + private PowerOff mPowerOff; + + // this is the BluetoothAdapter state that reported externally + private int mPublicState; + + BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService, + BluetoothAdapter bluetoothAdapter) { + super(TAG); + mContext = context; + mBluetoothService = bluetoothService; + mEventLoop = new BluetoothEventLoop(context, bluetoothAdapter, bluetoothService, this); + + mBluetoothOn = new BluetoothOn(); + mSwitching = new Switching(); + mHotOff = new HotOff(); + mWarmUp = new WarmUp(); + mPowerOff = new PowerOff(); + + addState(mBluetoothOn); + addState(mSwitching); + addState(mHotOff); + addState(mWarmUp); + addState(mPowerOff); + setInitialState(mPowerOff); + mPublicState = BluetoothAdapter.STATE_OFF; + + if (mContext.getResources().getBoolean + (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { + sendMessage(TURN_HOT); + } + } + + /** + * Bluetooth module's power is off, firmware is not loaded. + */ + private class PowerOff extends State { + private boolean mPersistSwitchOn = false; + + @Override + public void enter() { + if (DBG) log("Enter PowerOff: " + mPersistSwitchOn); + mPersistSwitchOn = false; + } + @Override + public boolean processMessage(Message message) { + if (DBG) log("PowerOff process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case USER_TURN_ON: + // starts turning on BT module, broadcast this out + transitionTo(mWarmUp); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + if (prepareBluetooth()) { + // this is user request, save the setting + if ((Boolean) message.obj) { + mPersistSwitchOn = true; + } + // We will continue turn the BT on all the way to the BluetoothOn state + deferMessage(obtainMessage(TURN_ON_CONTINUE)); + } else { + Log.e(TAG, "failed to prepare bluetooth, abort turning on"); + transitionTo(mPowerOff); + broadcastState(BluetoothAdapter.STATE_OFF); + } + break; + case TURN_HOT: + if (prepareBluetooth()) { + transitionTo(mWarmUp); + } + break; + case AIRPLANE_MODE_OFF: + if (getBluetoothPersistedSetting()) { + // starts turning on BT module, broadcast this out + transitionTo(mWarmUp); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + if (prepareBluetooth()) { + // We will continue turn the BT on all the way to the BluetoothOn state + deferMessage(obtainMessage(TURN_ON_CONTINUE)); + transitionTo(mWarmUp); + } else { + Log.e(TAG, "failed to prepare bluetooth, abort turning on"); + transitionTo(mPowerOff); + broadcastState(BluetoothAdapter.STATE_OFF); + } + } + break; + case AIRPLANE_MODE_ON: // ignore + case USER_TURN_OFF: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + /** + * Turn on Bluetooth Module, Load firmware, and do all the preparation + * needed to get the Bluetooth Module ready but keep it not discoverable + * and not connectable. + * The last step of this method sets up the local service record DB. + * There will be a event reporting the status of the SDP setup. + */ + private boolean prepareBluetooth() { + if (mBluetoothService.enableNative() != 0) { + return false; + } + + // try to start event loop, give 2 attempts + int retryCount = 2; + boolean eventLoopStarted = false; + while ((retryCount-- > 0) && !eventLoopStarted) { + mEventLoop.start(); + // it may take a moment for the other thread to do its + // thing. Check periodically for a while. + int pollCount = 5; + while ((pollCount-- > 0) && !eventLoopStarted) { + if (mEventLoop.isEventLoopRunning()) { + eventLoopStarted = true; + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + break; + } + } + } + + if (!eventLoopStarted) { + mBluetoothService.disableNative(); + return false; + } + + // get BluetoothService ready + if (!mBluetoothService.prepareBluetooth()) { + mEventLoop.stop(); + mBluetoothService.disableNative(); + return false; + } + + return true; + } + } + + /** + * Turning on Bluetooth module's power, loading firmware, starting + * event loop thread to listen on Bluetooth module event changes. + */ + private class WarmUp extends State { + + @Override + public void enter() { + if (DBG) log("Enter WarmUp"); + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log("WarmUp process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case SERVICE_RECORD_LOADED: + transitionTo(mHotOff); + break; + case USER_TURN_ON: // handle this at HotOff state + case TURN_ON_CONTINUE: // Once in HotOff state, continue turn bluetooth + // on to the BluetoothOn state + case AIRPLANE_MODE_ON: + case AIRPLANE_MODE_OFF: + deferMessage(message); + break; + case USER_TURN_OFF: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + } + + /** + * Bluetooth Module has powered, firmware loaded, event loop started, + * SDP loaded, but the modules stays non-discoverable and + * non-connectable. + */ + private class HotOff extends State { + private boolean mPersistSwitchOn = false; + + @Override + public void enter() { + if (DBG) log("Enter HotOff: " + mPersistSwitchOn); + mPersistSwitchOn = false; + } + + @Override + public boolean processMessage(Message message) { + if (DBG) log("HotOff process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case USER_TURN_ON: + if ((Boolean) message.obj) { + mPersistSwitchOn = true; + } + // let it fall to TURN_ON_CONTINUE: + case TURN_ON_CONTINUE: + mBluetoothService.switchConnectable(true); + transitionTo(mSwitching); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + break; + case AIRPLANE_MODE_ON: + case TURN_COLD: + mBluetoothService.shutoffBluetooth(); + mEventLoop.stop(); + transitionTo(mPowerOff); + // ASSERT no support of config_bluetooth_adapter_quick_switch + broadcastState(BluetoothAdapter.STATE_OFF); + break; + case AIRPLANE_MODE_OFF: + if (getBluetoothPersistedSetting()) { + mBluetoothService.switchConnectable(true); + transitionTo(mSwitching); + broadcastState(BluetoothAdapter.STATE_TURNING_ON); + } + break; + case USER_TURN_OFF: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + } + + private class Switching extends State { + + @Override + public void enter() { + int what = getCurrentMessage().what; + if (DBG) log("Enter Switching: " + what); + } + @Override + public boolean processMessage(Message message) { + if (DBG) log("Switching process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case BECOME_PAIRABLE: + if (mPowerOff.mPersistSwitchOn || mHotOff.mPersistSwitchOn) { + persistSwitchSetting(true); + mPowerOff.mPersistSwitchOn = mHotOff.mPersistSwitchOn = false; + } + String[] propVal = {"Pairable", mBluetoothService.getProperty("Pairable")}; + mEventLoop.onPropertyChanged(propVal); + + // run bluetooth now that it's turned on + mBluetoothService.runBluetooth(); + transitionTo(mBluetoothOn); + broadcastState(BluetoothAdapter.STATE_ON); + break; + case BECOME_NON_PAIRABLE: + if (mBluetoothService.getAdapterConnectionState() == + BluetoothAdapter.STATE_DISCONNECTED) { + transitionTo(mHotOff); + finishSwitchingOff(); + } + break; + case ALL_DEVICES_DISCONNECTED: + if (mBluetoothService.getScanMode() == BluetoothAdapter.SCAN_MODE_NONE) { + transitionTo(mHotOff); + finishSwitchingOff(); + } + break; + case USER_TURN_ON: + case AIRPLANE_MODE_OFF: + case AIRPLANE_MODE_ON: + case USER_TURN_OFF: + deferMessage(message); + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + private void finishSwitchingOff() { + if (mBluetoothOn.mPersistBluetoothOff) { + persistSwitchSetting(false); + mBluetoothOn.mPersistBluetoothOff = false; + } + mBluetoothService.finishDisable(); + if (mContext.getResources().getBoolean + (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { + broadcastState(BluetoothAdapter.STATE_OFF); + } else { + deferMessage(obtainMessage(TURN_COLD)); + } + } + } + + private class BluetoothOn extends State { + private boolean mPersistBluetoothOff = false; + + @Override + public void enter() { + if (DBG) log("Enter BluetoothOn: " + mPersistBluetoothOff); + mPersistBluetoothOff = false; + } + @Override + public boolean processMessage(Message message) { + if (DBG) log("BluetoothOn process message: " + message.what); + + boolean retValue = HANDLED; + switch(message.what) { + case USER_TURN_OFF: + if ((Boolean) message.obj) { + mPersistBluetoothOff = true; + } + // let it fall through to AIRPLANE_MODE_ON + case AIRPLANE_MODE_ON: + transitionTo(mSwitching); + broadcastState(BluetoothAdapter.STATE_TURNING_OFF); + mBluetoothService.switchConnectable(false); + mBluetoothService.disconnectDevices(); + // we turn all the way to PowerOff with AIRPLANE_MODE_ON + if (message.what == AIRPLANE_MODE_ON) { + deferMessage(obtainMessage(AIRPLANE_MODE_ON)); + } + break; + case AIRPLANE_MODE_OFF: // ignore + case USER_TURN_ON: // ignore + break; + default: + return NOT_HANDLED; + } + return retValue; + } + + } + + /** + * Return the public BluetoothAdapter state + */ + int getBluetoothAdapterState() { + return mPublicState; + } + + BluetoothEventLoop getBluetoothEventLoop() { + return mEventLoop; + } + + private void persistSwitchSetting(boolean setOn) { + long origCallerIdentityToken = Binder.clearCallingIdentity(); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.BLUETOOTH_ON, + setOn ? 1 : 0); + Binder.restoreCallingIdentity(origCallerIdentityToken); + } + + private boolean getBluetoothPersistedSetting() { + ContentResolver contentResolver = mContext.getContentResolver(); + return (Settings.Secure.getInt(contentResolver, + Settings.Secure.BLUETOOTH_ON, 0) > 0); + } + + private void broadcastState(int newState) { + + if (DBG) log("Bluetooth state " + mPublicState + " -> " + newState); + if (mPublicState == newState) { + return; + } + + Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mPublicState); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mPublicState = newState; + + mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM); + } + + private void dump(PrintWriter pw) { + IState currentState = getCurrentState(); + if (currentState == mPowerOff) { + pw.println("Bluetooth OFF - power down\n"); + } else if (currentState == mWarmUp) { + pw.println("Bluetooth OFF - warm up\n"); + } else if (currentState == mHotOff) { + pw.println("Bluetooth OFF - hot but off\n"); + } else if (currentState == mSwitching) { + pw.println("Bluetooth Switching\n"); + } else if (currentState == mBluetoothOn) { + pw.println("Bluetooth ON\n"); + } else { + pw.println("ERROR: Bluetooth UNKNOWN STATE "); + } + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index f345a6a..107a2a9 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -52,6 +52,7 @@ class BluetoothEventLoop { private final HashMap<String, Integer> mAuthorizationAgentRequestData; private final BluetoothService mBluetoothService; private final BluetoothAdapter mAdapter; + private final BluetoothAdapterStateMachine mBluetoothState; private BluetoothA2dp mA2dp; private BluetoothInputDevice mInputDevice; private final Context mContext; @@ -107,9 +108,11 @@ class BluetoothEventLoop { private static native void classInitNative(); /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, - BluetoothService bluetoothService) { + BluetoothService bluetoothService, + BluetoothAdapterStateMachine bluetoothState) { mBluetoothService = bluetoothService; mContext = context; + mBluetoothState = bluetoothState; mPasskeyAgentRequestData = new HashMap<String, Integer>(); mAuthorizationAgentRequestData = new HashMap<String, Integer>(); mAdapter = adapter; @@ -299,8 +302,8 @@ class BluetoothEventLoop { /** * Called by native code on a PropertyChanged signal from - * org.bluez.Adapter. This method is also called from Java at - * {@link BluetoothService.EnableThread#run()} to set the "Pairable" + * org.bluez.Adapter. This method is also called from + * {@link BluetoothAdapterStateMachine} to set the "Pairable" * property when Bluetooth is enabled. * * @param propValues a string array containing the key and one or more @@ -334,6 +337,15 @@ class BluetoothEventLoop { return; adapterProperties.setProperty(name, propValues[1]); + + if (name.equals("Pairable")) { + if (pairable.equals("true")) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.BECOME_PAIRABLE); + } else { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.BECOME_NON_PAIRABLE); + } + } + int mode = BluetoothService.bluezStringToScanMode( pairable.equals("true"), discoverable.equals("true")); diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index d68d8ba..34f1971 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -91,7 +91,7 @@ public class BluetoothService extends IBluetooth.Stub { private BluetoothPan mPan; private boolean mIsAirplaneSensitive; private boolean mIsAirplaneToggleable; - private int mBluetoothState; + private BluetoothAdapterStateMachine mBluetoothState; private boolean mRestart = false; // need to call enable() after disable() private boolean mIsDiscovering; private int[] mAdapterSdpHandles; @@ -111,9 +111,8 @@ public class BluetoothService extends IBluetooth.Stub { private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address"; private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings"; - private static final int MESSAGE_FINISH_DISABLE = 1; - private static final int MESSAGE_UUID_INTENT = 2; - private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3; + private static final int MESSAGE_UUID_INTENT = 1; + private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 2; // The time (in millisecs) to delay the pairing attempt after the first // auto pairing attempt fails. We use an exponential delay with @@ -206,7 +205,6 @@ public class BluetoothService extends IBluetooth.Stub { disableNative(); } - mBluetoothState = BluetoothAdapter.STATE_OFF; mIsDiscovering = false; mBondState = new BluetoothBondState(context, this); @@ -306,7 +304,9 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized void initAfterRegistration() { mAdapter = BluetoothAdapter.getDefaultAdapter(); - mEventLoop = new BluetoothEventLoop(mContext, mAdapter, this); + mBluetoothState = new BluetoothAdapterStateMachine(mContext, this, mAdapter); + mBluetoothState.start(); + mEventLoop = mBluetoothState.getBluetoothEventLoop(); } public synchronized void initAfterA2dpRegistration() { @@ -329,16 +329,16 @@ public class BluetoothService extends IBluetooth.Stub { } private boolean isEnabledInternal() { - return mBluetoothState == BluetoothAdapter.STATE_ON; + return (getBluetoothStateInternal() == BluetoothAdapter.STATE_ON); } public int getBluetoothState() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return mBluetoothState; + return getBluetoothStateInternal(); } int getBluetoothStateInternal() { - return mBluetoothState; + return mBluetoothState.getBluetoothAdapterState(); } /** @@ -356,7 +356,9 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean disable(boolean saveSetting) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - switch (mBluetoothState) { + int adapterState = getBluetoothStateInternal(); + + switch (adapterState) { case BluetoothAdapter.STATE_OFF: return true; case BluetoothAdapter.STATE_ON: @@ -364,27 +366,12 @@ public class BluetoothService extends IBluetooth.Stub { default: return false; } - if (mEnableThread != null && mEnableThread.isAlive()) { - return false; - } - - setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF); - - if (mAdapterSdpHandles != null) removeReservedServiceRecordsNative(mAdapterSdpHandles); - setBluetoothTetheringNative(false, BluetoothPanProfileHandler.NAP_ROLE, - BluetoothPanProfileHandler.NAP_BRIDGE); - - // Allow 3 seconds for profiles to gracefully disconnect - // TODO: Introduce a callback mechanism so that each profile can notify - // BluetoothService when it is done shutting down - disconnectDevices(); - mHandler.sendMessageDelayed( - mHandler.obtainMessage(MESSAGE_FINISH_DISABLE, saveSetting ? 1 : 0, 0), 3000); + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_OFF, saveSetting); return true; } - private synchronized void disconnectDevices() { + synchronized void disconnectDevices() { // Disconnect devices handled by BluetoothService. for (BluetoothDevice device: getConnectedInputDevices()) { disconnectInputDevice(device); @@ -395,14 +382,11 @@ public class BluetoothService extends IBluetooth.Stub { } } - private synchronized void finishDisable(boolean saveSetting) { - if (mBluetoothState != BluetoothAdapter.STATE_TURNING_OFF) { - return; - } - mEventLoop.stop(); - tearDownNativeDataNative(); - disableNative(); - + /** + * The Bluetooth has been turned off, but hot. Do bonding, profile, + * and internal cleanup + */ + synchronized void finishDisable() { // mark in progress bondings as cancelled for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) { mBondState.setBondState(address, BluetoothDevice.BOND_NONE, @@ -430,12 +414,6 @@ public class BluetoothService extends IBluetooth.Stub { mAdapterUuids = null; mAdapterSdpHandles = null; - if (saveSetting) { - persistBluetoothOnSetting(false); - } - - setBluetoothState(BluetoothAdapter.STATE_OFF); - // Log bluetooth off to battery stats. long ident = Binder.clearCallingIdentity(); try { @@ -451,6 +429,18 @@ public class BluetoothService extends IBluetooth.Stub { } } + /** + * power off Bluetooth + */ + synchronized void shutoffBluetooth() { + tearDownNativeDataNative(); + disableNative(); + if (mRestart) { + mRestart = false; + enable(); + } + } + /** Bring up BT and persist BT on in settings */ public boolean enable() { return enable(true); @@ -471,21 +461,29 @@ public class BluetoothService extends IBluetooth.Stub { if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) { return false; } - if (mBluetoothState != BluetoothAdapter.STATE_OFF) { - return false; - } - if (mEnableThread != null && mEnableThread.isAlive()) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON, saveSetting); + return true; + } + + /** + * Turn on Bluetooth Module, Load firmware, and do all the preparation + * needed to get the Bluetooth Module ready but keep it not discoverable + * and not connectable. + */ + /* package */ synchronized boolean prepareBluetooth() { + if (!setupNativeDataNative()) { return false; } - setBluetoothState(BluetoothAdapter.STATE_TURNING_ON); - mEnableThread = new EnableThread(saveSetting); - mEnableThread.start(); + mIsDiscovering = false; + + switchConnectable(false); + updateSdpRecords(); return true; } /** Forcibly restart Bluetooth if it is on */ /* package */ synchronized void restart() { - if (mBluetoothState != BluetoothAdapter.STATE_ON) { + if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) { return; } mRestart = true; @@ -494,30 +492,10 @@ public class BluetoothService extends IBluetooth.Stub { } } - private synchronized void setBluetoothState(int state) { - if (state == mBluetoothState) { - return; - } - - if (DBG) Log.d(TAG, "Bluetooth state " + mBluetoothState + " -> " + state); - - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mBluetoothState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, state); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - - mBluetoothState = state; - - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - } - private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MESSAGE_FINISH_DISABLE: - finishDisable(msg.arg1 != 0); - break; case MESSAGE_UUID_INTENT: String address = (String)msg.obj; if (address != null) { @@ -545,65 +523,6 @@ public class BluetoothService extends IBluetooth.Stub { } }; - private EnableThread mEnableThread; - - private class EnableThread extends Thread { - private final boolean mSaveSetting; - public EnableThread(boolean saveSetting) { - mSaveSetting = saveSetting; - } - public void run() { - boolean res = (enableNative() == 0); - if (res) { - int retryCount = 2; - boolean running = false; - while ((retryCount-- > 0) && !running) { - mEventLoop.start(); - // it may take a moment for the other thread to do its - // thing. Check periodically for a while. - int pollCount = 5; - while ((pollCount-- > 0) && !running) { - if (mEventLoop.isEventLoopRunning()) { - running = true; - break; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) {} - } - } - if (!running) { - Log.e(TAG, "bt EnableThread giving up"); - res = false; - disableNative(); - } - } - - if (res) { - if (!setupNativeDataNative()) { - return; - } - if (mSaveSetting) { - persistBluetoothOnSetting(true); - } - - mIsDiscovering = false; - mBondState.readAutoPairingData(); - mBondState.initBondState(); - initProfileState(); - - // This should be the last step of the the enable thread. - // Because this adds SDP records which asynchronously - // broadcasts the Bluetooth On State in updateBluetoothState. - // So we want all internal state setup before this. - updateSdpRecords(); - } else { - setBluetoothState(BluetoothAdapter.STATE_OFF); - } - mEnableThread = null; - } - } - private synchronized void addReservedSdpRecords(final ArrayList<ParcelUuid> uuids) { //Register SDP records. int[] svcIdentifiers = new int[uuids.size()]; @@ -650,38 +569,37 @@ public class BluetoothService extends IBluetooth.Stub { * for adapter comes in with UUID property. * @param uuidsThe uuids of adapter as reported by Bluez. */ - synchronized void updateBluetoothState(String uuids) { - if (mBluetoothState == BluetoothAdapter.STATE_TURNING_ON) { - ParcelUuid[] adapterUuids = convertStringToParcelUuid(uuids); - - if (mAdapterUuids != null && - BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) { - setBluetoothState(BluetoothAdapter.STATE_ON); - autoConnect(); - String[] propVal = {"Pairable", getProperty("Pairable")}; - mEventLoop.onPropertyChanged(propVal); - - // Log bluetooth on to battery stats. - long ident = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteBluetoothOn(); - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } + /*package*/ synchronized void updateBluetoothState(String uuids) { + ParcelUuid[] adapterUuids = convertStringToParcelUuid(uuids); - if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) { - disable(false); - } - } + if (mAdapterUuids != null && + BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SERVICE_RECORD_LOADED); } } - private void persistBluetoothOnSetting(boolean bluetoothOn) { - long origCallerIdentityToken = Binder.clearCallingIdentity(); - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, - bluetoothOn ? 1 : 0); - Binder.restoreCallingIdentity(origCallerIdentityToken); + /** + * This method is called immediately after Bluetooth module is turned on. + * It starts auto-connection and places bluetooth on sign onto the battery + * stats + */ + /*package*/ void runBluetooth() { + mIsDiscovering = false; + mBondState.readAutoPairingData(); + mBondState.initBondState(); + initProfileState(); + + autoConnect(); + + // Log bluetooth on to battery stats. + long ident = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteBluetoothOn(); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + Binder.restoreCallingIdentity(ident); + } } /*package*/ synchronized boolean attemptAutoPair(String address) { @@ -818,6 +736,26 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean setScanMode(int mode, int duration) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, "Need WRITE_SECURE_SETTINGS permission"); + return setScanMode(mode, duration, true); + } + + /** + * @param on true set the local Bluetooth module to be connectable + * but not dicoverable + * false set the local Bluetooth module to be not connectable + * and not dicoverable + */ + /*package*/ synchronized void switchConnectable(boolean on) { + if (on) { + // 0 is a dummy value, does not apply for SCAN_MODE_CONNECTABLE + setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, 0, false); + } else { + // 0 is a dummy value, does not apply for SCAN_MODE_NONE + setScanMode(BluetoothAdapter.SCAN_MODE_NONE, 0, false); + } + } + + private synchronized boolean setScanMode(int mode, int duration, boolean allowOnlyInOnState) { boolean pairable; boolean discoverable; @@ -839,9 +777,15 @@ public class BluetoothService extends IBluetooth.Stub { Log.w(TAG, "Requested invalid scan mode " + mode); return false; } - setPropertyBoolean("Pairable", pairable); - setPropertyBoolean("Discoverable", discoverable); + if (allowOnlyInOnState) { + setPropertyBoolean("Pairable", pairable); + setPropertyBoolean("Discoverable", discoverable); + } else { + // allowed to set the property through native layer directly + setAdapterPropertyBooleanNative("Pairable", pairable ? 1 : 0); + setAdapterPropertyBooleanNative("Discoverable", discoverable ? 1 : 0); + } return true; } @@ -1569,14 +1513,10 @@ public class BluetoothService extends IBluetooth.Stub { ContentResolver resolver = context.getContentResolver(); // Query the airplane mode from Settings.System just to make sure that // some random app is not sending this intent and disabling bluetooth - boolean enabled = !isAirplaneModeOn(); - // If bluetooth is currently expected to be on, then enable or disable bluetooth - if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) { - if (enabled) { - enable(false); - } else { - disable(false); - } + if (isAirplaneModeOn()) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_ON); + } else { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_OFF); } } else if (Intent.ACTION_DOCK_EVENT.equals(action)) { int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, @@ -1650,8 +1590,7 @@ public class BluetoothService extends IBluetooth.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - dumpBluetoothState(pw); - if (mBluetoothState != BluetoothAdapter.STATE_ON) { + if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) { return; } @@ -1840,25 +1779,6 @@ public class BluetoothService extends IBluetooth.Stub { } } - private void dumpBluetoothState(PrintWriter pw) { - switch(mBluetoothState) { - case BluetoothAdapter.STATE_OFF: - pw.println("Bluetooth OFF\n"); - break; - case BluetoothAdapter.STATE_TURNING_ON: - pw.println("Bluetooth TURNING ON\n"); - break; - case BluetoothAdapter.STATE_TURNING_OFF: - pw.println("Bluetooth TURNING OFF\n"); - break; - case BluetoothAdapter.STATE_ON: - pw.println("Bluetooth ON\n"); - break; - default: - pw.println("Bluetooth UNKNOWN STATE " + mBluetoothState); - } - } - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = new BluetoothProfile.ServiceListener() { public void onServiceConnected(int profile, BluetoothProfile proxy) { @@ -2389,7 +2309,7 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized void sendConnectionStateChange(BluetoothDevice device, int state, int prevState) { // Since this is a binder call check if Bluetooth is on still - if (mBluetoothState == BluetoothAdapter.STATE_OFF) return; + if (getBluetoothStateInternal() == BluetoothAdapter.STATE_OFF) return; if (updateCountersAndCheckForConnectionStateChange(state, prevState)) { if (!validateProfileConnectionState(state) || @@ -2405,6 +2325,10 @@ public class BluetoothService extends IBluetooth.Stub { mAdapterConnectionState = state; + if (state == BluetoothProfile.STATE_DISCONNECTED) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.ALL_DEVICES_DISCONNECTED); + } + Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, @@ -2598,8 +2522,8 @@ public class BluetoothService extends IBluetooth.Stub { /*package*/ native String getAdapterPathNative(); private native int isEnabledNative(); - private native int enableNative(); - private native int disableNative(); + /*package*/ native int enableNative(); + /*package*/ native int disableNative(); /*package*/ native Object[] getAdapterPropertiesNative(); private native Object[] getDevicePropertiesNative(String objectPath); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2b2f356..65dce49 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -469,6 +469,10 @@ speech --> <bool name="config_bluetooth_wide_band_speech">true</bool> + <!-- Boolean indicating if current platform supports quick switch-on/off of + Bluetooth Module --> + <bool name="config_bluetooth_adapter_quick_switch">true</bool> + <!-- The default data-use polling period. --> <integer name="config_datause_polling_period_sec">600</integer> |