blob: d57498b3ec52d7cfdf3662aec479215266ad919b [file] [log] [blame]
package com.android.managedprovisioning.task;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import com.android.managedprovisioning.ProvisionLogger;
import com.android.managedprovisioning.ProvisioningParams;
import com.android.managedprovisioning.comm.ProvisionCommLogger;
import com.android.managedprovisioning.proxy.BluetoothConnectionService;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Connect to a remote programmer device over Bluetooth. The default Bluetooth adapter is enabled
* and then used to connect to the remote device. This connection is used to send status updates
* and to share network access so that the device that is being provisioned can use the programmer
* device's Internet connection. This task is only run if provisioning was started via NFC bump and
* if that bump contains the required Bluetooth fields.
*/
public class BluetoothConnectTask {
/** Time to wait (in milliseconds) for the Bluetooth adapter to be enabled. */
private static final int BT_ENABLE_TIMEOUT_MS = 45000;
private final Context mContext;
private final Callback mCallback;
/**
* Handler used to execute {@link #onBluetoothEnabled()} if called from a {@linkplain
* #mBtStateChangeReceiver broadcast receiver}.
*/
private final Handler mHandler;
private final AtomicBoolean mTaskDone;
private final boolean mUseBluetoothProxy;
private final String mBluetoothMac;
private final String mBluetoothUuid;
private final String mBluetoothDeviceId;
private final boolean mHasWifiSsid;
private final BluetoothAdapter mBtAdapter;
/**
* Listens for changes in the state of the BluetoothAdapter. Used to wait for Bluetooth to
* be enabled. This will be initialized to {@code null} and set to a non-{@code null} value it
* it has been registered.
*/
private BroadcastReceiver mBtStateChangeReceiver;
/**
* Receives notification when the Bluetooth network proxy is set up. If the Bluetooth network
* proxy is required, this task will wait for the proxy to be enabled and for network access to
* be available.
*/
private BroadcastReceiver mBtNetworkProxyReceiver;
public BluetoothConnectTask(Context context, ProvisioningParams params, Callback callback) {
mCallback = callback;
mContext = context;
// Use the default Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// Read provisioning parameters
mBluetoothMac = params.mBluetoothMac;
mBluetoothUuid = params.mBluetoothUuid;
mBluetoothDeviceId = params.mBluetoothDeviceIdentifier;
mUseBluetoothProxy = params.mUseBluetoothProxy;
mHasWifiSsid = !TextUtils.isEmpty(params.mWifiSsid);
// Handler used to execute onBluetoothEnabled
HandlerThread thread = new HandlerThread("Timeout thread",
android.os.Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
Looper looper = thread.getLooper();
mHandler = new Handler(looper);
mTaskDone = new AtomicBoolean();
}
public void run() {
// The adapter will be null if Bluetooth is not supported
if (mBtAdapter == null) {
mCallback.onSuccess();
return;
}
// If any of the required fields are invalid, do not connect over Bluetooth.
if (TextUtils.isEmpty(mBluetoothMac) || TextUtils.isEmpty(mBluetoothUuid) ||
TextUtils.isEmpty(mBluetoothDeviceId)) {
mCallback.onSuccess();
return;
}
if (mBtAdapter.isEnabled()) {
ProvisionLogger.logd("Bluetooth already enabled.");
onBluetoothEnabled();
} else {
ProvisionLogger.logd("Attempt to enable Bluetooth.");
mBtStateChangeReceiver = createStateChangeReceiver();
mContext.registerReceiver(mBtStateChangeReceiver,
new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
if (!mBtAdapter.enable()) {
cleanUp();
mCallback.onError();
return;
}
// Set a timeout for receiving the Bluetooth enabled broadcast
mHandler.postDelayed(new Runnable(){
@Override
public void run() {
if (mTaskDone.getAndSet(true)) {
ProvisionLogger.logd("Timeout received but already succeeded.");
return;
}
ProvisionLogger.loge("Timed out waiting for Bluetooth.");
cleanUp();
mCallback.onError();
}
},
BT_ENABLE_TIMEOUT_MS);
}
}
/**
* Create a {@code BroadcastReceiver} that waits for Bluetooth to be enabled. After calling
* {@link BluetoothAdapter#enable()}, we must wait for the adapter to turn on before using
* any Bluetooth functionality. This receiver will call {@link #onBluetoothEnabled()} when
* the state changes.
* @return a broadcast receiver that waits for Bluetooth to be enabled
*/
private BroadcastReceiver createStateChangeReceiver() {
return new BroadcastReceiver() {
// Prevents onBluetoothEnabled() from being called more than once.
private boolean mHasBeenEnabled;
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.STATE_OFF);
if (state == BluetoothAdapter.STATE_ON && !mHasBeenEnabled) {
mHasBeenEnabled = true;
ProvisionLogger.logd("Received bluetooth enabled status.");
mHandler.post(new Runnable() {
@Override
public void run() {
onBluetoothEnabled();
}
});
}
}
}
};
}
/**
* Creates a Bluetooth connection with a remote device. Run after Bluetooth has been enabled.
*/
private void onBluetoothEnabled() {
ProvisionLogger.logd("Bluetooth enabled.");
// Discovery will lower Bluetooth connection bandwidth. It should be canceled.
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
// Don't start the Bluetooth proxy if a Wi-Fi network is available. If an SSID is available,
// assume that a Wi-Fi network will be added and do not start the network proxy. If a
// Wi-Fi connection is already available, do not use the proxy.
ProvisionLogger.logd("useProxy: mUseBluetoothProxy=" + mUseBluetoothProxy +
", mHasWifiSsid=" + mHasWifiSsid +
", isConnectedToWifi=" + AddWifiNetworkTask.isConnectedToWifi(mContext));
boolean useProxy = mUseBluetoothProxy && !mHasWifiSsid &&
!AddWifiNetworkTask.isConnectedToWifi(mContext);
// Start Bluetooth Service
if (useProxy) {
// This task won't succeed until the network proxy is enabled. Other tasks may require
// the network connection provided by this proxy. This receiver will be notified
// by the BluetoothConnectionService.
mBtNetworkProxyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ProvisionCommLogger.logd("Bluetooth proxy; Setup complete");
onBluetoothSetUp();
}
};
LocalBroadcastManager.getInstance(mContext).registerReceiver(
mBtNetworkProxyReceiver,
new IntentFilter(BluetoothConnectionService.ACTION_LOCAL_BLUETOOTH_SETUP));
}
Intent intent = new Intent(mContext, BluetoothConnectionService.class);
intent.putExtra(BluetoothConnectionService.EXTRA_BLUETOOTH_MAC, mBluetoothMac);
intent.putExtra(BluetoothConnectionService.EXTRA_BLUETOOTH_UUID, mBluetoothUuid);
intent.putExtra(BluetoothConnectionService.EXTRA_BLUETOOTH_DEVICE_ID, mBluetoothDeviceId);
intent.putExtra(BluetoothConnectionService.EXTRA_BLUETOOTH_USE_PROXY, useProxy);
mContext.startService(intent);
if (!useProxy) {
// A network connection will be established some other way.
onBluetoothSetUp();
}
}
/**
* Called when the Bluetooth connection has been enabled.
*/
private void onBluetoothSetUp() {
// Clean up receivers and call on success.
if (mTaskDone.getAndSet(true)) {
ProvisionLogger.logd("Bluetooth enabled but already timed out.");
return;
}
cleanUp();
mCallback.onSuccess();
}
/**
* Unregister receivers.
*/
public void cleanUp() {
if (mBtStateChangeReceiver != null) {
mContext.unregisterReceiver(mBtStateChangeReceiver);
mBtStateChangeReceiver = null;
}
if (mBtNetworkProxyReceiver != null) {
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mBtNetworkProxyReceiver);
mBtNetworkProxyReceiver = null;
}
mHandler.removeCallbacksAndMessages(null);
}
public abstract static class Callback {
public abstract void onSuccess();
public abstract void onError();
}
}