Commit ffae7588 authored by Craig Lafayette's avatar Craig Lafayette
Browse files

Remove Bluetooth provisioning.

Bug: 21559093
Change-Id: Ibcb41b4be8c9ef2019273a2c1be516607bc9daa4
parent 8988e976
......@@ -3,24 +3,16 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 \
ManagedProvisioningComm
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
LOCAL_PACKAGE_NAME := ManagedProvisioning
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include frameworks/opt/setupwizard/library/common.mk
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
......@@ -138,10 +138,6 @@
android:name="DeviceOwnerProvisioningService" >
</service>
<service
android:name=".proxy.BluetoothConnectionService" >
</service>
<receiver android:name="com.android.managedprovisioning.BootReminder">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
......
# Copyright (C) 2007 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# If you don't need to do a full clean build but would like to touch
# a file or delete some intermediate files, add a clean step to the end
# of the list. These steps will only be run once, if they haven't been
# run before.
#
# E.g.:
# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
#
# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
# files that are missing or have been moved.
#
# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
# Use $(OUT_DIR) to refer to the "out" directory.
#
# If you need to re-do something that's already mentioned, just copy
# the command and add it to the bottom of the list. E.g., if a change
# that you made last week required touching a file and a change you
# made today requires touching the same file, just copy the old
# touch step and add it to the end of the list.
#
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
# For example:
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/ManagedProvisioningComm_intermediates)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
#
# Copyright (C) 2015 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.
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := guava
LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-proto-files-under, protos)
LOCAL_MODULE := ManagedProvisioningComm
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
/*
* Copyright (C) 2015 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.
*/
syntax = "proto2";
package bluetooth;
option java_package = "com.android.managedprovisioning.comm";
option optimize_for = LITE_RUNTIME;
// Provisioning status updates
message StatusUpdate {
optional int32 status_code = 1;
optional string custom_data = 2;
}
// Information about the connecting device
message DeviceInfo {
optional int32 api_version = 1;
optional string make = 2;
optional string model = 3;
optional string serial = 4;
optional string fingerprint = 5;
optional int64 totalMemory = 6;
optional int32 screenWidthPx = 7;
optional int32 screenHeightPx = 8;
optional float screenDensity = 9;
}
// Holds network data transferred over Bluetooth.
message NetworkData {
enum Status {
OK = 1; // Data is valid.
EOF = 2; // End of file reached.
SHUTDOWN = 3; // Connection ending; shut down all threads
}
optional int32 connection_id = 1;
optional Status status = 2 [default = OK];
optional bytes data = 3;
}
// Data packet sent and received over Bluetooth. The deviceIdentifier should
// always be set when sending a request. Only one of the other packets should
// be set.
message CommPacket {
optional string deviceIdentifier = 1;
optional StatusUpdate status_update = 2;
optional NetworkData network_data = 3;
optional DeviceInfo device_info = 4;
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import android.bluetooth.BluetoothAdapter;
import android.os.Handler;
import android.os.Looper;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* An Acceptor is a thread that will loop over the ServerSocketWrapper
* and accept connections. It will use the handler factory to spin up
* a Handler for each of those connections.
*/
public class BluetoothAcceptor extends Thread implements ProvisioningAcceptor {
/**
* Number of consecutive Bluetooth IOExceptions allowed before trying to recreate the
* Bluetooth server socket.
*/
private static final int IO_EXCEPTION_RECREATE = 5;
private final ServerSocketWrapper mServerSocket;
private final ChannelFactory mChannelFactory;
private volatile boolean mIsRunning;
private int mConsecutiveFails;
private boolean mDoRecreate;
/** User defined callback. */
private final StatusCallback mCallback;
/** Main thread handler. Used to post callback events. */
private final Handler mHandler;
/**
* Synchronized set of device ids expected to connect.
* @see #listenForDevice(String)
* @see #stopListening(String)
*/
private final Set<String> mExpectedDevices;
/**
* Create a new instance that communicates over Bluetooth. The {@link #startConnection()}
* method must be called before communication can begin. Defaults to using a
* {@link CommPacketChannelFactory}.
* @param adapter Bluetooth adapter used to establish a connection
* @param serviceName name of the created Bluetooth service, used for discovery
* @param uuid unique identifier of the created Bluetooth service, used for discovery
* @param callback callback that receives information about connected devices
*/
public BluetoothAcceptor(BluetoothAdapter adapter, String serviceName, UUID uuid,
StatusCallback callback) {
this(adapter, serviceName, uuid, callback, new CommPacketChannelFactory());
}
/**
* Create a new instance that communicates over Bluetooth. The {@link #startConnection()}
* method must be called before communication can begin.
* @param adapter Bluetooth adapter used to establish a connection
* @param serviceName name of the created Bluetooth service, used for discovery
* @param uuid unique identifier of the created Bluetooth service, used for discovery
* @param callback callback that receives information about connected devices
* @param channelFactory factory to create Channels for each new connection
*/
public BluetoothAcceptor(BluetoothAdapter adapter, String serviceName, UUID uuid,
StatusCallback callback, ChannelFactory channelFactory) {
mExpectedDevices = Collections.synchronizedSet(new HashSet<String>());
// Setup callback
mCallback = callback;
mHandler = new Handler(Looper.getMainLooper());
// Create socket
mServerSocket = new BluetoothServerSocketWrapper(serviceName, uuid, adapter);
mChannelFactory = channelFactory;
}
@Override
public void run() {
mIsRunning = true;
try {
while (mIsRunning) {
try {
SocketWrapper socket = mServerSocket.accept();
handleConnection(socket);
mConsecutiveFails = 0;
} catch (IOException e) {
if (mIsRunning) {
ProvisionCommLogger.logd(e);
}
++mConsecutiveFails;
if (mIsRunning && (mConsecutiveFails > IO_EXCEPTION_RECREATE || mDoRecreate)) {
mDoRecreate = false;
try {
mServerSocket.recreate();
} catch (IOException e1) {
ProvisionCommLogger.loge("Problem recreating server socket", e1);
}
}
}
}
} finally {
close();
}
}
@Override
public boolean isInProgress() {
return mIsRunning;
}
private void close() {
if (mServerSocket != null) {
try {
ProvisionCommLogger.logd("Closing acceptor acceptance task");
mIsRunning = false;
mServerSocket.close();
} catch (Exception e) {
ProvisionCommLogger.logd(e);
}
}
}
@Override
public synchronized void startConnection() throws IOException {
mServerSocket.recreate();
if (!mIsRunning) {
start();
mIsRunning = true;
}
}
@Override
public void stopConnection() {
close();
}
@Override
public void listenForDevice(String deviceIdentifier) {
mExpectedDevices.add(deviceIdentifier);
}
@Override
public void stopListening(String deviceIdentifier) {
mExpectedDevices.remove(deviceIdentifier);
}
/**
* Handle Bluetooth socket connection on a new thread.
* @param socket the Bluetooth connection
*/
private void handleConnection(SocketWrapper socket) {
new ProxyConnectionHandler(mChannelFactory.newInstance(socket), mHandler, mCallback,
Collections.unmodifiableSet(mExpectedDevices)).start();
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.text.TextUtils;
import java.io.IOException;
import java.util.UUID;
/**
* Wrapper around a {@link BluetoothServerSocket}.
*/
public class BluetoothServerSocketWrapper implements ServerSocketWrapper {
private final BluetoothAdapter mBtAdapter;
private final UUID mUuid;
private final String mServerName;
private BluetoothServerSocket mServerSocket;
/**
* Start listening for Bluetooth connections.
* @param serverName the name of server; used for Bluetooth Service Discovery Protocol.
* @param uuid unique identifier for the Service Discovery Protocol record.
* @param adapter Bluetooth adapter used for listening
* @throws NullPointerException if either {@code uuid} or {@code adapter} are null.
* @throws IllegalArgumentException if {@code serverName} is either {@code null} or empty.
*/
public BluetoothServerSocketWrapper(String serverName, UUID uuid,
BluetoothAdapter adapter) {
if (uuid == null || adapter == null) {
throw new NullPointerException("UUID and BluetoothAdapter cannot be null");
}
if (TextUtils.isEmpty(serverName)) {
throw new IllegalArgumentException("serverName cannot be empty");
}
mServerName = serverName;
mBtAdapter = adapter;
mUuid = uuid;
}
@Override
public SocketWrapper accept() throws IOException {
return new BluetoothSocketWrapper(mBtAdapter, mServerSocket.accept());
}
@Override
public void close() throws IOException {
if (mServerSocket != null) {
mServerSocket.close();
}
}
@Override
public void recreate() throws IOException {
try {
close();
} catch (Exception e) {
ProvisionCommLogger.logw(e);
}
mServerSocket = mBtAdapter.listenUsingInsecureRfcommWithServiceRecord(mServerName, mUuid);
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
/**
* Provides a testable wrapper around a {@code BluetoothSocket}.
*/
public class BluetoothSocketWrapper implements SocketWrapper {
private final BluetoothAdapter mBluetoothAdapter;
private BluetoothSocket mSocket;
private String mMacAddress;
private UUID mUuid;
// Used by BluetoothServerSocket when socket exists.
public BluetoothSocketWrapper(BluetoothAdapter adapter, BluetoothSocket socket) {
mBluetoothAdapter = adapter;
mSocket = socket;
}
// Used for clients so that a ReliableChannel can recreate the connection.
public BluetoothSocketWrapper(BluetoothAdapter adapter, String macAddress, String uuid) {
mBluetoothAdapter = adapter;
mMacAddress = macAddress;
mUuid = UUID.fromString(uuid);
}
@Override
public InputStream getInputStream() throws IOException {
return mSocket.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
return mSocket.getOutputStream();
}
@Override
public void close() throws IOException {
if (mSocket != null) {
try {
getInputStream().close();
} catch (IOException ex) {
ProvisionCommLogger.logw(ex);
}
try {
getOutputStream().close();
} catch (IOException ex) {
ProvisionCommLogger.logw(ex);
}
try {
mSocket.close();
} catch (IOException ex) {
ProvisionCommLogger.logw(ex);
}
}
}
@Override
public boolean isConnected() {
return mSocket != null && mSocket.isConnected();
}
@Override
public void open() throws IOException {
if (mMacAddress != null) {
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mMacAddress);
mSocket = device.createInsecureRfcommSocketToServiceRecord(mUuid);
}
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
mSocket.connect();
}
@Override
public String getIdentifier() {
return mSocket.getRemoteDevice().getAddress();
}
@Override
public void recreate() throws IOException {
if (mMacAddress == null) {
throw new IOException("Cannot recreate a socket with no MAC Address");
}
try {
close();
} catch (IOException e) {
}
open();
}
public void setReconnectUuid(String mBluetoothUuid) {
mUuid = UUID.fromString(mBluetoothUuid);
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import java.io.IOException;
/**
* A channel controls the I/O of a single socket. It handles reading from/writing to the socket, as
* well as parsing incoming messages and serializing outgoing messages.
*/
public interface Channel extends AutoCloseable {
/**
* Write a message to the socket.
* @param packet The message to be written
* @throws IOException If the write fails
*/
void write(Bluetooth.CommPacket packet) throws IOException;
/**
* Read a message from the socket.
* @return The parsed message
* @throws IOException If reading or parsing fails
*/
Bluetooth.CommPacket read() throws IOException;
@Override
/**
* Close this socket
*/
void close();
/**
* Determine if the socket connection held by this instance is connected.
* @return {@code true} if this socket is connected.
*/
boolean isConnected();
/**
* Flush the contents of the buffer.
* @throws IOException
*/
void flush() throws IOException;
/**
* Called when an error which could be retried is encountered.
* @throws IOException
*/
void reset() throws IOException;
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
/**
* A factory class for creating {@link Channel}s
*/
public interface ChannelFactory {
/**
* Create a new Channel object
* @param wrapper The socket which the Channel should wrap
* @return The new Channel
*/
Channel newInstance(SocketWrapper wrapper);
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import com.android.managedprovisioning.comm.Bluetooth.CommPacket;
import java.io.IOException;
/**
* A Handler is a reader thread that will loop over a thread reading
* messages and calling a handle function. It is designed to remove
* the repetitive looping nature from other classes.
*/
public abstract class ChannelHandler extends Thread {
private static final int MAX_IO_EXCEPTIONS = 10;
protected Channel mChannel;
private boolean mIsRunning;
public ChannelHandler(Channel socket) {
mChannel = socket;
mIsRunning = true;
}
@Override
public void run() {
int exceptionCount = 0;
try {
startConnection();
while (mIsRunning && mChannel.isConnected()
&& exceptionCount < MAX_IO_EXCEPTIONS) {
try {
CommPacket packet = mChannel.read();
handleRequest(packet);
} catch (IOException ioe) {
ProvisionCommLogger.logd(ioe);
exceptionCount++;
}
}
// Catch everything for graceful close.
} catch (Exception e) {
ProvisionCommLogger.loge(e);
} finally {
stopConnection();
if (mChannel != null) {
mChannel.close();
}
}
}
public void stopHandler() {
mIsRunning = false;
}
/**
* Action to take when starting connection.
* @throws IOException if there is an issue that should prevent the connection from starting
*/
protected abstract void startConnection() throws IOException;
/**
* Action to take when shutting down the connection.
*/
protected abstract void stopConnection();
/**
* Handle data sent over the communication channel.
* @param packet communication packet received
* @throws IOException if the packet could not be processed
*/
protected abstract void handleRequest(CommPacket packet) throws IOException;
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import com.android.managedprovisioning.comm.Bluetooth.CommPacket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.google.protobuf.nano.CodedInputByteBufferNano;
import com.google.protobuf.nano.CodedOutputByteBufferNano;
import com.google.protobuf.nano.MessageNano;
/**
* A CommPacketChannel is an implementation of {@link Channel} which allows communication over a
* SocketWrapper using Protobuf messages.
*/
public class CommPacketChannel implements Channel {
protected final SocketWrapper mSocket;
// Used to synchronize writes while allowing simultaneous read/writing.
protected final Object mWriteLock = new Object();
public CommPacketChannel(SocketWrapper socket) {
mSocket = socket;
}
// GuardedBy(mWriteLock)
@Override
public void write(CommPacket packet)
throws IOException {
synchronized (mWriteLock) {
OutputStream outputStream = mSocket.getOutputStream();
byte[] array = serializePacket(preparePacket(packet));
outputStream.write(array);
outputStream.flush();
}
}
/**
* Prepare a packet before serializing it. This exists to provide a hook for child classes that
* need to transform outgoing messages. This implementation is a no-op.
* @param packet The packet which is being sent
* @return The transformed packet
*/
protected MessageNano preparePacket(CommPacket packet) {
return packet;
}
/**
* Serialize a packet to a byte array. The returned byte array will be written to the socket.
* @param packet The packet to serialize.
* @return A byte array containing the serialized packet.
* @throws IOException If serialization fails due to a size mismatch.
*/
protected byte[] serializePacket(MessageNano packet) throws IOException {
int size = packet.getSerializedSize();
int delimitSize = CodedOutputByteBufferNano.computeRawVarint32Size(size);
byte[] array = new byte[size + delimitSize];
CodedOutputByteBufferNano outputBuffer = CodedOutputByteBufferNano.newInstance(array);
outputBuffer.writeRawVarint32(size);
packet.writeTo(outputBuffer);
if (outputBuffer.spaceLeft() != 0) {
throw new IOException("Incorrect size calculated");
}
return array;
}
@Override
public synchronized CommPacket read() throws IOException {
return read(mSocket.getInputStream());
}
@SuppressWarnings("unchecked")
protected synchronized CommPacket read(InputStream inputStream) throws IOException {
CodedInputByteBufferNano inputBuffer = readByteBuffer(inputStream);
try {
return readPacket(inputBuffer);
} catch (ClassCastException e) {
ProvisionCommLogger.loge("Incorrect type called for return value", e);
return null;
}
}
protected CodedInputByteBufferNano readByteBuffer(InputStream inputStream) throws IOException {
byte[] readBuffer = new byte[512];
int index = 0;
// Read bytes while the most significant bit is set. The CodedInputByteBufferNano from
// proto-nano only reads up to 10 bytes, so we will do the same.
do {
while (inputStream.read(readBuffer, index, 1) <= 0);
} while ((readBuffer[index++] < 0) && (index < 10));
CodedInputByteBufferNano inputBuffer =
CodedInputByteBufferNano.newInstance(readBuffer, 0, index);
int size = inputBuffer.readRawVarint32();
byte[] buffer = new byte[size];
int readIndex = 0;
while (readIndex < size) {
int amount = inputStream.read(buffer, readIndex, size - readIndex);
if (amount > 0) {
readIndex += amount;
}
}
return CodedInputByteBufferNano.newInstance(buffer);
}
protected CommPacket readPacket(CodedInputByteBufferNano inputBuffer)
throws IOException {
CommPacket packet = Bluetooth.CommPacket.parseFrom(inputBuffer);
return packet;
}
@Override
public void close() {
try {
mSocket.close();
} catch (IOException ioe) {
ProvisionCommLogger.logw(ioe);
}
}
/**
* Determine if the socket connection held by this instance is connected.
* @return {@code true} if this socket is connected.
*/
@Override
public boolean isConnected() {
return mSocket.isConnected();
}
/**
* Flushes the contents of the buffer. For unbuffered channels, this does nothing.
* @throws IOException
*/
@Override
public void flush() throws IOException {
}
/**
* Recreate the socket. Called when a retriable error is encountered.
* @throws IOException
*/
@Override
public void reset() throws IOException {
mSocket.recreate();
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
/**
* A factory for CommPacketChannels
*/
public class CommPacketChannelFactory implements ChannelFactory {
/**
* Create a new CommPacketChannel
* @param wrapper The socket which the Channel should wrap
* @return The new CommPacketChannel
*/
@Override
public Channel newInstance(SocketWrapper wrapper) {
return new CommPacketChannel(wrapper);
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.content.Context;
import android.os.Build;
import android.util.DisplayMetrics;
import com.android.managedprovisioning.comm.Bluetooth.StatusUpdate;
import com.android.managedprovisioning.comm.Bluetooth.DeviceInfo;
import com.android.managedprovisioning.comm.Bluetooth.NetworkData;
import com.android.managedprovisioning.comm.Bluetooth.CommPacket;
import java.util.Arrays;
/**
* Handles creation of common {@code CommPacket} protos.
*/
public class PacketUtil {
/** A connection id value that signals to close the connection. */
public static final int END_CONNECTION = -1;
/** Sent as part of each message to indicate which device sent a message. */
private final String mDeviceIdentifier;
public PacketUtil(String deviceIdentifier) {
mDeviceIdentifier = deviceIdentifier;
}
/**
* Create a communication packet containing a status update.
* @param statusCode the reported provisioning state
* @param customData extra data sent with the status update
*/
public CommPacket createStatusUpdate(int statusCode, String customData) {
StatusUpdate statusUpdate = new StatusUpdate();
statusUpdate.statusCode = statusCode;
statusUpdate.customData = nullSafe(customData);
// Create packet
CommPacket packet = new CommPacket();
packet.deviceIdentifier = mDeviceIdentifier;
packet.statusUpdate = statusUpdate;
return packet;
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public CommPacket createDeviceInfo(Context context) {
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.apiVersion = android.os.Build.VERSION.SDK_INT;
deviceInfo.make = nullSafe(android.os.Build.MANUFACTURER);
deviceInfo.model = nullSafe(android.os.Build.MODEL);
deviceInfo.serial = nullSafe(android.os.Build.SERIAL);
deviceInfo.fingerprint = nullSafe(android.os.Build.FINGERPRINT);
// Get memory info.
MemoryInfo mi = new MemoryInfo();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager != null) {
activityManager.getMemoryInfo(mi);
deviceInfo.totalMemory = mi.totalMem;
}
// Get screen info.
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
deviceInfo.screenWidthPx = metrics.widthPixels;
deviceInfo.screenHeightPx = metrics.heightPixels;
deviceInfo.screenDensity = metrics.density;
// Create packet
CommPacket packet = new CommPacket();
packet.deviceIdentifier = mDeviceIdentifier;
packet.deviceInfo = deviceInfo;
return packet;
}
public CommPacket createDataPacket(int connectionId, int status,
byte[] data, int len) {
NetworkData networkData = new NetworkData();
networkData.connectionId = connectionId;
networkData.status = status;
if (data != null) {
networkData.data = Arrays.copyOf(data, len);
}
// Create packet
CommPacket packet = new CommPacket();
packet.deviceIdentifier = mDeviceIdentifier;
packet.networkData = networkData;
return packet;
}
public CommPacket createEndPacket() {
return createDataPacket(END_CONNECTION, NetworkData.EOF, null, 0);
}
public CommPacket createEndPacket(int connId) {
return createDataPacket(connId, NetworkData.EOF, null, 0);
}
private static String nullSafe(String s) {
return s != null ? s : "";
}
}
/*
* Copyright 2015, 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.managedprovisioning.comm;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
/**
* Utility class to centralize the logging in the Provisioning app.
*/
public class ProvisionCommLogger {
private static final String TAG = "ManagedProvisioningComm";
private static final boolean LOG_ENABLED = true;
// Never commit this as true.
public static final boolean IS_DEBUG_BUILD = false;
/**
* Log the message at DEBUG level.
*/
public static void logd(String message) {
if (LOG_ENABLED) {
Log.d(getTag(), message);
}
}
/**
* Log the message at DEBUG level.
*/
public static void logd(String message, Throwable t) {
if (LOG_ENABLED) {
Log.d(getTag(), message, t);
}
}
/**
* Log the message at DEBUG level.
*/
public static void logd(Throwable t) {
if (LOG_ENABLED) {
Log.d(getTag(), "", t);
}
}
/**
* Log the message at VERBOSE level.
*/
public static void logv(String message) {
if (LOG_ENABLED) {
Log.v(getTag(), message);
}
}
/**
* Log the message at VERBOSE level.
*/
public static void logv(String message, Throwable t) {
if (LOG_ENABLED) {
Log.v(getTag(), message, t);
}
}
/**
* Log the message at VERBOSE level.
*/
public static void logv(Throwable t) {
if (LOG_ENABLED) {
Log.v(getTag(), "", t);
}
}
/**
* Log the message at INFO level.
*/
public static void logi(String message) {
if (LOG_ENABLED) {
Log.i(getTag(), message);
}
}
/**
* Log the message at INFO level.
*/
public static void logi(String message, Throwable t) {
if (LOG_ENABLED) {
Log.i(getTag(), message, t);
}
}
/**
* Log the message at INFO level.
*/
public static void logi(Throwable t) {
if (LOG_ENABLED) {
Log.i(getTag(), "", t);
}
}
/**
* Log the message at WARNING level.
*/
public static void logw(String message) {
if (LOG_ENABLED) {
Log.w(getTag(), message);
}
}
/**
* Log the message at WARNING level.
*/
public static void logw(String message, Throwable t) {
if (LOG_ENABLED) {
Log.w(getTag(), message, t);
}
}
/**
* Log the message at WARNING level.
*/
public static void logw(Throwable t) {
if (LOG_ENABLED) {
Log.w(getTag(), "", t);
}
}
/**
* Log the message at ERROR level.
*/
public static void loge(String message) {
if (LOG_ENABLED) {
Log.e(getTag(), message);
}
}
/**
* Log the message at ERROR level.
*/
public static void loge(String message, Throwable t) {
if (LOG_ENABLED) {
Log.e(getTag(), message, t);
}
}
/**
* Log the message at ERROR level.
*/
public static void loge(Throwable t) {
if (LOG_ENABLED) {
Log.e(getTag(), "", t);
}
}
/**
* Walks the stack trace to figure out where the logging call came from.
*/
static String getTag() {
if (IS_DEBUG_BUILD) {
String className = ProvisionCommLogger.class.getName();
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (trace == null) {
return TAG;
}
boolean thisClassFound = false;
for (StackTraceElement item : trace) {
if (item.getClassName().equals(className)) {
// we are at the current class, keep eating all items from this
// class.
thisClassFound = true;
continue;
}
if (thisClassFound) {
// This is the first instance of another class, which is most
// likely the caller class.
return TAG + String.format(
"[%s(%s): %s]", item.getFileName(), item.getLineNumber(),
item.getMethodName());
}
}
}
return TAG;
}
public static void toast(Context context, String toast) {
if (IS_DEBUG_BUILD) {
Toast.makeText(context, toast, Toast.LENGTH_LONG).show();
}
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import java.io.IOException;
/**
* Receives status updates from remote devices.
*/
public interface ProvisioningAcceptor {
/**
* @return {@code true} if currently accepting connections
*/
boolean isInProgress();
/**
* Start listening for connections from a device with the specified identifier. A device will
* not be allowed to connect if this method is not called with its identifier. This value is
* user defined and should be non-null. When a device is no longer expected to connect, or
* should be prevented from connecting in the future, {@link #stopListening(String)} should
* be called.
* @param deviceIdentifier expected device identifier
*/
void listenForDevice(String deviceIdentifier);
/**
* Begin accepting connections.
* @throws IOException is setting up listener fails
*/
void startConnection() throws IOException;
/**
* Stop accepting connections.
*/
void stopConnection();
/**
* Prevent a device with the specified identifier from connecting.
* @param deviceIdentifier device identifier for the device that shouldn't be allowed to
* connect in the future.
*/
void stopListening(String deviceIdentifier);
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import com.android.managedprovisioning.comm.Bluetooth.NetworkData;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* The connection between a channel and a socket to a web server. This connection handles most
* compliant proxy clients. It expects an initial CONNECT request
* {@see http://tools.ietf.org/html/rfc2817#section-5.2}. Additionally, it can handle clients which
* omit the CONNECT request, as long as they specify an absoluteURI in their request line.
* {@see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html}.
*
* If a client does not send a CONNECT request, and attempts to make a request using an
* abs_path {@see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html} in the request line, the
* connection will be rejected.
*/
public class ProxyConnection extends Thread {
private static final String CONNECT = "CONNECT";
private static final String HTTPS = "https";
private static final String RESPONSE_OK = "HTTP/1.1 200 OK\n\n";
private NetToBtThread mNetToBt;
private BtToNetThread mBtToNet;
private volatile boolean mNetRunning;
private Socket mNetSocket;
private final PipedInputStream mHttpInput;
private final OutputStream mHttpOutput;
private final int mConnId;
private final Channel mChannel;
/**
* Used to create network data response packets. The device Id can be empty because this is
* called from the programmer device.
*/
private final PacketUtil mPacketUtil = new PacketUtil("");
public ProxyConnection(Channel channel, int connId) {
mChannel = channel;
mConnId = connId;
mHttpInput = new PipedInputStream();
mHttpOutput = new PipedOutputStream();
try {
mHttpInput.connect((PipedOutputStream) mHttpOutput);
} catch (IOException e) {
// The streams were just created so this shouldn't happen.
ProvisionCommLogger.loge(e);
}
mNetRunning = true;
}
public boolean isRunning() {
return mNetRunning;
}
public void shutdown() {
try {
mHttpOutput.close();
} catch (IOException io) {
ProvisionCommLogger.logd(io);
}
endConnection();
}
@Override
public void run() {
processConnect();
}
private void endConnection() {
try {
mChannel.write(mPacketUtil.createEndPacket(mConnId));
} catch (IOException io) {
ProvisionCommLogger.logd("Could not write closing packet.", io);
}
try {
if (mNetSocket != null) {
mNetSocket.close();
}
} catch (IOException io) {
ProvisionCommLogger.logd("Attempted to close socket when already closed.", io);
}
}
private class NetToBtThread extends Thread {
@Override
public void run() {
final byte[] buffer = new byte[16384];
InputStream input = null;
try {
input = mNetSocket.getInputStream();
while (mNetSocket.isConnected()) {
int readBytes = input.read(buffer);
if (readBytes < 0) {
mChannel.write(mPacketUtil.createEndPacket(mConnId));
break;
}
mChannel.write(mPacketUtil.createDataPacket(mConnId, NetworkData.OK, buffer,
readBytes));
}
} catch (IOException io) {
ProvisionCommLogger.logd("Server socket input stream is closed.");
} finally {
if (input != null) {
try {
input.close();
} catch (IOException ex) {
ProvisionCommLogger.logw(
"Failed to close connection", ex);
}
}
}
mNetRunning = false;
}
}
private class BtToNetThread extends Thread {
@Override
public void run() {
final byte[] buffer = new byte[16384];
try {
while (true) {
int readBytes = mHttpInput.read(buffer);
if (readBytes < 0) {
break;
}
if (mNetSocket == null) {
break;
} else {
mNetSocket.getOutputStream().write(buffer, 0, readBytes);
}
}
} catch (IOException io) {
ProvisionCommLogger.logd("Bluetooth input stream for this connection is closed.");
} finally {
try {
mHttpInput.close();
} catch (IOException ex) {
ProvisionCommLogger.logw("Failed to close connection", ex);
}
}
}
}
private String getLine() throws IOException {
StringBuilder buffer = new StringBuilder();
int ch;
while ((ch = mHttpInput.read()) != -1) {
if (ch == '\r')
continue;
if (ch == '\n')
break;
buffer.append((char) ch);
}
return buffer.toString();
}
@VisibleForTesting
protected static class RequestLineInfo {
String method;
URI uri;
public RequestLineInfo(String method, URI uri) {
this.method = method;
this.uri = uri;
}
}
/**
* Parse a request line. Supports CONNECT requests, as well as other requests using absoluteURI
* {@see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html}
* {@see http://tools.ietf.org/html/rfc2817#section-5.2}
* {@see https://www.ietf.org/rfc/rfc2396.txt}
* @param requestLine The requestLine from the HTTP request
* @return A struct containing the parsed request if parsing was successful, otherwise null.
*/
@VisibleForTesting
protected static RequestLineInfo parseRequestLine(String requestLine) {
String[] split = requestLine.split(" ");
if (split.length < 2) {
ProvisionCommLogger.loge("Could not parse request line: " + requestLine);
return null;
}
String method = split[0];
String uriString = split[1];
if (CONNECT.equals(method)) {
// CONNECT request lines come through as an 'authority' element (see RFC 2396), which do
// not contain a scheme. We don't need the scheme to open the socket, but we do need it
// for the ProxySelector - so force it to HTTPS.
if (!uriString.contains("://")) {
uriString = HTTPS + "://" + uriString;
}
}
URI uri;
try {
// parse with URL first - this is more restrictive, and catches the case where a GET
// request comes through with an abs_path, but no host, in the request line. This
// situation is unsupported by this proxy (but should never happen with a compliant
// client - we should always see a CONNECT request first).
URL url = new URL(uriString);
uri = url.toURI();
} catch (MalformedURLException|URISyntaxException e) {
ProvisionCommLogger.loge(
"Invalid or unsupported URI in request line: " + requestLine, e);
return null;
}
if (uri.getPort() < 0) {
// If no port was specified, choose a default
int newPort = 80;
if (CONNECT.equals(method) || HTTPS.equals(uri.getScheme().toLowerCase())) {
newPort = 443;
}
try {
// sadly this is the only way to "mutate" a URI
uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), newPort,
uri.getPath(), uri.getQuery(), uri.getFragment());
} catch (URISyntaxException e) {
ProvisionCommLogger.loge(
"Invalid URI when trying to enforce https: " + requestLine, e);
return null;
}
}
return new RequestLineInfo(method, uri);
}
/**
* Create a socket to the requested host. Check for an applicable proxy, and use it if found.
* @param uri URI containing the host to connect to.
* @return
* @throws IOException
*/
private boolean createSocket(URI uri) throws IOException {
boolean usingProxy = false;
String host = uri.getHost();
int port = uri.getPort();
List<Proxy> list = ProxySelector.getDefault().select(uri);
for (Proxy proxy : list) {
if (proxy.equals(Proxy.NO_PROXY)) {
break; // break out and create a normal socket
} else {
if (proxy.address() instanceof InetSocketAddress) {
// Only Inets created by PacProxySelector and ProxySelectorImpl.
InetSocketAddress inetSocketAddress =
(InetSocketAddress)proxy.address();
// A proxy specified with an IP addr should only ever use that IP. This
// will ensure that the proxy only ever connects to its specified
// address. If the proxy is resolved, use the associated IP address. If
// unresolved, use the specified host name.
host = inetSocketAddress.isUnresolved() ?
inetSocketAddress.getHostName() :
inetSocketAddress.getAddress().getHostAddress();
port = inetSocketAddress.getPort();
usingProxy = true;
break;
} else {
ProvisionCommLogger.logw("Unsupported Inet type from ProxySelector, skipping:" +
proxy.address().getClass().getSimpleName());
}
}
}
mNetSocket = new Socket(host, port);
return usingProxy;
}
private void processConnect() {
try {
String requestLine = getLine() + '\r' + '\n';
RequestLineInfo info = parseRequestLine(requestLine);
if (info == null) {
mNetRunning = false;
return;
}
boolean usingProxy;
try {
usingProxy = createSocket(info.uri);
} catch (IOException e) {
ProvisionCommLogger.loge("Failed to create socket: " +
info.uri.getHost() + ":" + info.uri.getPort(), e);
mNetRunning = false;
mNetSocket = null;
return;
}
if (CONNECT.equals(info.method) && !usingProxy) {
// If we're not talking to a proxy, and we're handling a CONNECT, we need to
// send a response
handleConnect();
} else {
// Otherwise, pass through the request line, since we already read it from the
// stream. The BtToNetThread will shuffle the rest of the request along when it
// starts up.
mNetSocket.getOutputStream().write(requestLine.getBytes());
}
mNetToBt = new NetToBtThread();
mNetToBt.start();
mBtToNet = new BtToNetThread();
mBtToNet.start();
} catch (Exception e) {
ProvisionCommLogger.logd("Error processing request", e);
mNetRunning = false;
}
}
public void closePipe() {
try {
mHttpInput.close();
} catch (IOException e) {
ProvisionCommLogger.logd(e);
}
try {
mHttpOutput.close();
} catch (IOException e) {
ProvisionCommLogger.logd(e);
}
}
private void handleConnect() throws IOException {
while (getLine().length() != 0);
// No proxy to respond so we must.
mChannel.write(mPacketUtil.createDataPacket(mConnId, NetworkData.OK,
RESPONSE_OK.getBytes(),
RESPONSE_OK.length()));
}
public OutputStream getOutput() {
return mHttpOutput;
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import android.os.Handler;
import com.android.managedprovisioning.comm.Bluetooth.CommPacket;
import com.android.managedprovisioning.comm.Bluetooth.DeviceInfo;
import com.android.managedprovisioning.comm.Bluetooth.NetworkData;
import com.android.managedprovisioning.comm.Bluetooth.StatusUpdate;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Set;
/**
* Handles all input from a single Channel, which may be receiving packets from multiple proxy
* connections on the student-side tablet. Distinct proxy connections are identified by connection
* IDs; each connection is processed by its own Connection thread, which passes packets along to the
* appropriate server, and sends server responses back over Bluetooth to the student-side proxy
* connection. This is a component of the Bluetooth-mediated proxy server system.
*/
public class ProxyConnectionHandler extends ChannelHandler {
private final Hashtable<Integer, ProxyConnection> mConnectionTable;
/**
* Set of device identifiers that are expected to connect. Packets without expected device
* identifiers will be ignored and their connection attempts rejected.
*/
private final Set<String> mExpectedConnections;
private final StatusCallback mCallback;
private final Handler mCallbackHandler;
public ProxyConnectionHandler(Channel channel, Handler handler, StatusCallback callback,
Set<String> expectedConnections) {
super(channel);
if (callback == null) {
callback = new StatusCallback();
}
mCallback = callback;
mCallbackHandler = handler;
mConnectionTable = new Hashtable<Integer, ProxyConnection>();
mExpectedConnections = expectedConnections;
}
private void endConnection() throws IOException {
ProvisionCommLogger.logd("Ending bluetooth connection.");
// Acknowledge EOC received by returning message. This writes a packet without a device Id
try {
mChannel.write(new PacketUtil("").createEndPacket());
} finally {
mChannel.close();
}
}
@Override
protected void startConnection() throws IOException {
}
@Override
protected void stopConnection() {
try {
for (ProxyConnection connection : mConnectionTable.values()) {
connection.shutdown();
}
} catch (Exception e) {
ProvisionCommLogger.logd("Problem cleaning up connection", e);
}
}
@Override
protected void handleRequest(CommPacket packet) throws IOException {
// Make sure device identifier is expected
String deviceIdentifier = packet.deviceIdentifier;
if (deviceIdentifier == null || !mExpectedConnections.contains(deviceIdentifier)) {
ProvisionCommLogger.logd("Unexpected device: " + deviceIdentifier);
endConnection();
return;
}
// Process packet. Make sure only a single extra packet type is specified.
if (packet.deviceInfo != null) {
if (packet.networkData != null || packet.statusUpdate != null) {
ProvisionCommLogger.logd("Device " + deviceIdentifier + " set multiple packets.");
endConnection();
return;
}
handleDeviceInfoPacket(deviceIdentifier, packet.deviceInfo);
} else if (packet.networkData != null) {
if (packet.statusUpdate != null) {
ProvisionCommLogger.logd("Device " + deviceIdentifier + " set multiple packets.");
endConnection();
return;
}
handleNetworkDataPacket(packet.networkData);
} else if (packet.statusUpdate != null) {
handleStatusUpdatePacket(deviceIdentifier, packet.statusUpdate);
}
}
private void handleDeviceInfoPacket(final String deviceIdentifier,
final DeviceInfo deviceInfo) {
mCallbackHandler.post(new Runnable() {
@Override
public void run() {
try {
mCallback.onDeviceCheckin(deviceIdentifier, deviceInfo);
} catch (Throwable t) {
ProvisionCommLogger.logd("Error from callback.", t);
}
}
});
}
private void handleNetworkDataPacket(NetworkData networkData) throws IOException {
if (networkData.connectionId == PacketUtil.END_CONNECTION) {
endConnection();
return;
}
ProxyConnection connection = mConnectionTable.get(networkData.connectionId);
if (connection == null) {
ProvisionCommLogger.logv("Adding a stream for connection #" + networkData.connectionId);
connection = new ProxyConnection(mChannel, networkData.connectionId);
mConnectionTable.put(networkData.connectionId, connection);
connection.start();
}
if (networkData.status == NetworkData.EOF) {
ProvisionCommLogger.logv("Read EOF for conn #" + networkData.connectionId);
connection.shutdown();
} else {
connection.getOutput().write(networkData.data);
connection.getOutput().flush();
}
}
private void handleStatusUpdatePacket(final String deviceIdentifier,
final StatusUpdate statusUpdate) {
mCallbackHandler.post(new Runnable() {
@Override
public void run() {
try {
mCallback.onStatusUpdate(deviceIdentifier, statusUpdate);
} catch (Throwable t) {
ProvisionCommLogger.logd("Error from callback.", t);
}
}
});
}
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import java.io.IOException;
/**
* Provides an abstraction layer that wraps a BluetoothServerSocket.
*/
public interface ServerSocketWrapper {
/**
* Restart the underlying connection.
* @throws IOException if the connection could not be reestablished.
*/
void recreate() throws IOException;
/**
* Listen for a Bluetooth connection. This method will block until connected.
* @return the connection
* @throws IOException if there was an error while connecting.
*/
SocketWrapper accept() throws IOException;
/**
* Stop listening for incoming connections.
* @throws IOException if there was an error while closing the connection.
*/
void close() throws IOException;
}
/*
* Copyright (C) 2015 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.managedprovisioning.comm;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* SocketWrapper is an abstraction layer around a Bluetooth socket.
*/
public interface SocketWrapper {
InputStream getInputStream() throws IOException;
OutputStream getOutputStream() throws IOException;
void open() throws IOException;
void close() throws IOException;
boolean isConnected();
void recreate() throws IOException;
String getIdentifier();
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment