I have source code which is able to scan for BLE devies which are temp sensors, but when scanning it will not show any other BLE decies. I want to modify this code attached so that I can scan and view all BLE devices, regardless if it is a temp sensor. Can someone please explain me or show me how i can do this. here are the codes snippets to grab BLE Temp sensor data.
package no.nordicsemi.android.nrftemp.ble;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import no.nordicsemi.android.nrftemp.ble.parser.TemperatureData;
import no.nordicsemi.android.nrftemp.ble.parser.TemperatureDataParser;
import no.nordicsemi.android.nrftemp.database.DatabaseHelper;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.os.Handler;
public class TemperatureManager implements BluetoothAdapter.LeScanCallback {
/** An minimal interval between each data row in the database for a single device */
private static final long DATA_INTERVAL = 60 * 5 * 1000L; // ms
private BluetoothAdapter mBluetoothAdapter;
private DatabaseHelper mDatabaseHelper;
private List<TemperatureManagerCallback> mListeners;
public TemperatureManager(final Context context, final DatabaseHelper databaseHelper) {
final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
mDatabaseHelper = databaseHelper;
mListeners = new ArrayList<TemperatureManagerCallback>(2);
}
public void addCallback(final TemperatureManagerCallback callback) {
mListeners.add(callback);
}
public void removeCallback(final TemperatureManagerCallback callback) {
mListeners.remove(callback);
}
private void fireOnDevicesScanned() {
for (TemperatureManagerCallback callback : mListeners)
callback.onDevicesScaned();
}
private void fireOnRssiUpdate(final long sensorId, final int rssi) {
for (TemperatureManagerCallback callback : mListeners)
callback.onRssiUpdate(sensorId, rssi);
}
/**
* Starts scanning for temperature data. Call {#link #stopScan()} when done to save the power.
*/
public void startScan() {
mBluetoothAdapter.startLeScan(this);
}
/**
* Starts scanning for temperature data. Call {#link #stopScan()} when done to save the power.
*/
public void startScan(final long period) {
mBluetoothAdapter.startLeScan(this);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
}, period);
}
/**
* Stops scanning for temperature data from BLE sensors.
*/
public void stopScan() {
mBluetoothAdapter.stopLeScan(this);
}
#Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
final TemperatureData td = TemperatureDataParser.parse(device, scanRecord);
if (!td.isValid())
return;
final long now = Calendar.getInstance().getTimeInMillis();
final long then = mDatabaseHelper.getLastTimestamp(td.getAddress());
if (now - then > DATA_INTERVAL) {
mDatabaseHelper.addNewSensorData(device.getAddress(), device.getName(), now,
td.getTemp(), td.getBattery());
fireOnDevicesScanned();
}
final long sensorId = mDatabaseHelper.findSensor(device.getAddress());
final int rssiPercent = (int) (100.0f * (127.0f + rssi) / 127.0f + 20.0f);
mDatabaseHelper.updateSensorRssi(sensorId, rssiPercent);
fireOnRssiUpdate(sensorId, rssiPercent);
}
/**
* Return <code>true</code> if Bluetooth is currently enabled and ready for use.
*
* #return <code>true</code> if the local adapter is turned on
*/
public boolean isEnabled() {
return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
}
}
package no.nordicsemi.android.nrftemp.ble.parser;
import android.bluetooth.BluetoothDevice;
/**
* Domain class contains data obtained from a single advertising package from a temperature sensor.
*/
public class TemperatureData {
/** The device address */
private final String address;
/** The device name */
private String name;
/** The temperature value */
private double temp;
/** Battery status (number 0-100 in percent) */
private int battery = 0xFF; // unknown value
/**
* The flag whether the data are valid (holds real temperature measurement or not)
*/
private boolean valid;
public TemperatureData(BluetoothDevice device) {
address = device.getAddress();
name = device.getName();
}
public String getAddress() {
return address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getTemp() {
return temp;
}
public void setTemp(double temp) {
this.temp = temp;
this.valid = true;
}
public int getBattery() {
return battery;
}
public void setBattery(int battery) {
this.battery = battery;
}
public boolean isValid() {
return valid;
}
}
package no.nordicsemi.android.nrftemp.ble.parser;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import android.bluetooth.BluetoothDevice;
import android.util.Log;
public class TemperatureDataParser {
private static final String TAG = "TemperatureDataParser";
private static final int FLAGS = 0x02; // "Flags" Data Type (See section 18.1 of Core_V4.0.pdf)
private static final int LOCAL_NAME = 0x09; // "Complete Local Name" Data Type (See section 18.3 of Core_V4.0.pdf)
private static final int SERVICE_DATA = 0x16; // "Service Data" Data Type (See section 18.10 of Core_V4.0.pdf)
private static final short TEMPERATURE_SERVICE_UUID = 0x1809;
private static final short BATTERY_SERVICE_UUID = 0x180F;
private static final short DEVICE_INFORMATION_SERVICE_UUID = 0x180A;
/**
* Parses the advertising package obtained by BLE device
*
* #param data
* the data obtained (EIR data). Read Bluetooth Core Specification v4.0 (Core_V4.0.pdf -> Vol.3 -> Part C -> Section 8) for details
* #return the parsed temperature data
* #throws ParseException
* thrown when the given data does not fit the expected format
*/
public static TemperatureData parse(final BluetoothDevice device, final byte[] data) {
final TemperatureData td = new TemperatureData(device);
/*
* First byte of each EIR Data Structure has it's length (1 octet).
* There comes the EIR Data Type (n bytes) and (length - n bytes) of data.
* See Core_V4.0.pdf -> Vol.3 -> Part C -> Section 8 for details
*/
for (int i = 0; i < data.length;) {
final int eirLength = data[i];
// check whether there is no more to read
if (eirLength == 0)
break;
final int eirDataType = data[++i];
switch (eirDataType) {
case FLAGS:
// do nothing
break;
case LOCAL_NAME:
/*
* Local name data structure contains length - 1 bytes of the device name (1 byte for the data type)
*/
final String name = decodeLocalName(data, i + 1, eirLength - 1);
td.setName(name);
break;
case SERVICE_DATA:
/*
* First 2 bytes of service data are the 16-bit Service UUID in reverse order. The rest is service specific data.
*/
final short serviceUUID = decodeServiceUUID(data, i + 1);
switch (serviceUUID) {
case BATTERY_SERVICE_UUID:
final int battery = decodeBatteryLevel(data, i + 3);
td.setBattery(battery);
break;
case TEMPERATURE_SERVICE_UUID:
final double temp = decodeTempLevel(data, i + 3);
td.setTemp(temp);
break;
case DEVICE_INFORMATION_SERVICE_UUID:
// do nothing
break;
}
break;
default:
break;
}
i += eirLength;
}
return td;
}
private static String decodeLocalName(final byte[] data, final int start, final int length) {
try {
return new String(data, start, length, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unable to convert the local name to UTF-8", e);
return null;
}
}
private static short decodeServiceUUID(final byte[] data, final int start) {
return (short) ((data[start + 1] << 8) | data[start]);
}
private static int decodeBatteryLevel(final byte[] data, final int start) {
/*
* Battery level is a 1 byte number 0-100 (0x64). Value 255 (0xFF) is used for illegal measurement values
*/
return data[start];
}
private static float decodeTempLevel(final byte[] data, final int start) {
return bytesToFloat(data[start], data[start + 1], data[start + 2], data[start + 3]);
}
/**
* Convert signed bytes to a 32-bit short float value.
*/
private static float bytesToFloat(byte b0, byte b1, byte b2, byte b3) {
int mantissa = unsignedToSigned(unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8) + (unsignedByteToInt(b2) << 16), 24);
return (float) (mantissa * Math.pow(10, b3));
}
/**
* Convert a signed byte to an unsigned int.
*/
private static int unsignedByteToInt(byte b) {
return b & 0xFF;
}
/**
* Convert an unsigned integer value to a two's-complement encoded signed value.
*/
private static int unsignedToSigned(int unsigned, int size) {
if ((unsigned & (1 << size - 1)) != 0) {
unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1)));
}
return unsigned;
}
}
As far as I can see, the code is scanning for every BLE device already, there are no service uuid restrictions in the scanning mBluetoothAdapter.startLeScan(this);
The issue with this code (as with many examples) is that it assumes every device to be of their own type (Temperature sensor in this case).
The assumption is here:
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
final TemperatureData td = TemperatureDataParser.parse(device, scanRecord);
The only way to know what services a device support is by connecting to it, do service discovery and the check for the supported services.
Regards, Rob.
Related
I am having trouble using startLeScan( new UUID[]{ MY_DESIRED_128_BIT_SERVICE_UUID }, callback ) on the new introduced BLE API of Android 4.3 on my Nexus 4.
The callback just doesn't get called. I still can see incoming packages in the log:
08-02 15:48:57.985: I/bt-hci(1051): btu_ble_process_adv_pkt
08-02 15:48:58.636: I/bt-hci(1051): BLE HCI(id=62) event = 0x02)
If I don't use the parameter to filter for UUIDs it works. We are using a manufacturer specific 128bit UUID for device of our company.
Now, our device offers more services than I am providing in the array. But that shouldn't be the problem.
Is anyone facing the same problem? Any solutions?
Edit
There are several problems related to scanning, this question only discusses one: If you also have some issue with scanning, read this comment first. Also keep in mind, that my device imposes a 16bit and a 128bit UUID. Most of you guys use 16bit UUIDs provided by the BLE standard like Heart rate or Speed and Cadence.
#Navin's code is good, but it includes an overflow bug from the original 16-bit Android code. (If either byte is larger than 127 then it becomes a negative integer.)
Here's an implementation which fixes that bug and adds 128-bit support:
private List<UUID> parseUuids(byte[] advertisedData) {
List<UUID> uuids = new ArrayList<UUID>();
ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN);
while (buffer.remaining() > 2) {
byte length = buffer.get();
if (length == 0) break;
byte type = buffer.get();
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
while (length >= 2) {
uuids.add(UUID.fromString(String.format(
"%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
length -= 2;
}
break;
case 0x06: // Partial list of 128-bit UUIDs
case 0x07: // Complete list of 128-bit UUIDs
while (length >= 16) {
long lsb = buffer.getLong();
long msb = buffer.getLong();
uuids.add(new UUID(msb, lsb));
length -= 16;
}
break;
default:
buffer.position(buffer.position() + length - 1);
break;
}
}
return uuids;
}
Try this to retrieve/filter the device from the advertised 128-bit UUIDs:
private List<UUID> parseUUIDs(final byte[] advertisedData) {
List<UUID> uuids = new ArrayList<UUID>();
int offset = 0;
while (offset < (advertisedData.length - 2)) {
int len = advertisedData[offset++];
if (len == 0)
break;
int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
while (len > 1) {
int uuid16 = advertisedData[offset++];
uuid16 += (advertisedData[offset++] << 8);
len -= 2;
uuids.add(UUID.fromString(String.format(
"%08x-0000-1000-8000-00805f9b34fb", uuid16)));
}
break;
case 0x06:// Partial list of 128-bit UUIDs
case 0x07:// Complete list of 128-bit UUIDs
// Loop through the advertised 128-bit UUID's.
while (len >= 16) {
try {
// Wrap the advertised bits and order them.
ByteBuffer buffer = ByteBuffer.wrap(advertisedData,
offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
long mostSignificantBit = buffer.getLong();
long leastSignificantBit = buffer.getLong();
uuids.add(new UUID(leastSignificantBit,
mostSignificantBit));
} catch (IndexOutOfBoundsException e) {
// Defensive programming.
Log.e(LOG_TAG, e.toString());
continue;
} finally {
// Move the offset to read the next uuid.
offset += 15;
len -= 16;
}
}
break;
default:
offset += (len - 1);
break;
}
}
return uuids;
}
This is a reported bug at least in Android 4.3 JWR66Y:
Filtering works, if I provide my 16bit UUID
Filtering doesn't return any scan results, if I provide my 128bit UUID or if I provide both UUIDs
My setting: My device offers 2 UUIDs on advertising (1 16bit and 1 128bit) and 4 UUIDs on service discovery (1 128bit and 3 16bit).
Even if it gets fixed, I warn everybody against using the filter option provided by Android. For backward compatibility and since it's broken on Samsung Galaxy S3 with Android 4.3
Although 4.3 doesn't seem to support filtering by 128-bit UUIDs, these UUIDs are likely present in the byte[] scanRecord returned by the LeScanCallback.
There's probably a correct way to parse this data, but if you're getting the same data each time you can filter the results manually by finding the offsets of the UUIDs you're looking for. You might do this by printing the scan data to a log (as a hex string) and looking for the UUIDs you're interested in (they will probably follow a 0x06 or 0x07 and will be reversed). Once you find the offset, it shouldn't be too hard to set up a basic filter.
Here is a simple example that filters by a single UUID (uses Apache Commons Lang for ArrayUtils and the bytes-to-hex method found here, but you can substitute your own code where necessary)
public static boolean hasMyService(byte[] scanRecord) {
// UUID we want to filter by (without hyphens)
final String myServiceID = "0000000000001000800000805F9B34FB";
// The offset in the scan record. In my case the offset was 13; it will probably be different for you
final int serviceOffset = 13;
try{
// Get a 16-byte array of what may or may not be the service we're filtering for
byte[] service = ArrayUtils.subarray(scanRecord, serviceOffset, serviceOffset + 16);
// The bytes are probably in reverse order, so we need to fix that
ArrayUtils.reverse(service);
// Get the hex string
String discoveredServiceID = bytesToHex(service);
// Compare against our service
return myServiceID.equals(discoveredServiceID);
} catch (Exception e){
return false;
}
}
Are you certain that the peripheral is listing the specified service UUID in the advertisement data or scan response data?
The best method to list out the Service UUIDs from the scan result is to copy exactly the parseFromBytes method from ScanRecord.java which is located inside the android.bluetooth.le package (make sure you have the latest Android SDK), change the return to a List of ParcelUuid since that's the only thing we care about
private static final int DATA_TYPE_FLAGS = 0x01;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
private static final int DATA_TYPE_SERVICE_DATA = 0x16;
private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
public static List<ParcelUuid> parseFromBytes(byte[] scanRecord) {
if (scanRecord == null) {
return null;
}
int currentPos = 0;
int advertiseFlag = -1;
List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
String localName = null;
int txPowerLevel = Integer.MIN_VALUE;
SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>();
Map<ParcelUuid, byte[]> serviceData = new HashMap<ParcelUuid, byte[]>();
try {
while (currentPos < scanRecord.length) {
// length is unsigned int.
int length = scanRecord[currentPos++] & 0xFF;
if (length == 0) {
break;
}
// Note the length includes the length of the field type itself.
int dataLength = length - 1;
// fieldType is unsigned int.
int fieldType = scanRecord[currentPos++] & 0xFF;
switch (fieldType) {
case DATA_TYPE_FLAGS:
advertiseFlag = scanRecord[currentPos] & 0xFF;
break;
case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos,
dataLength, 2, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength,
BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength,
BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
break;
case DATA_TYPE_LOCAL_NAME_SHORT:
case DATA_TYPE_LOCAL_NAME_COMPLETE:
localName = new String(
extractBytes(scanRecord, currentPos, dataLength));
break;
case DATA_TYPE_TX_POWER_LEVEL:
txPowerLevel = scanRecord[currentPos];
break;
case DATA_TYPE_SERVICE_DATA:
// The first two bytes of the service data are service data UUID in little
// endian. The rest bytes are service data.
int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
serviceUuidLength);
ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
serviceDataUuidBytes);
byte[] serviceDataArray = extractBytes(scanRecord,
currentPos + serviceUuidLength, dataLength - serviceUuidLength);
serviceData.put(serviceDataUuid, serviceDataArray);
break;
case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
// The first two bytes of the manufacturer specific data are
// manufacturer ids in little endian.
int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) +
(scanRecord[currentPos] & 0xFF);
byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
dataLength - 2);
manufacturerData.put(manufacturerId, manufacturerDataBytes);
break;
default:
// Just ignore, we don't handle such data type.
break;
}
currentPos += dataLength;
}
if (serviceUuids.isEmpty()) {
serviceUuids = null;
}
// Log.i("SERVICE UUIDS", parcelUuidToString(serviceUuids));
} catch (Exception e) {
Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
// As the record is invalid, ignore all the parsed results for this packet
// and return an empty record with raw scanRecord bytes in results
}
return serviceUuids;
}
You would need to import BluetoothUuid.java from the same package as well:
import java.util.UUID;
import android.os.ParcelUuid;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.HashSet;
/**
* Static helper methods and constants to decode the ParcelUuid of remote devices.
* #hide
*/
public final class BluetoothUuid {
/* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs
* for the various services.
*
* The following 128 bit values are calculated as:
* uuid * 2^96 + BASE_UUID
*/
public static final ParcelUuid AudioSink =
ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid AudioSource =
ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid AdvAudioDist =
ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid HSP =
ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid HSP_AG =
ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid Handsfree =
ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid Handsfree_AG =
ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid AvrcpController =
ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid AvrcpTarget =
ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid ObexObjectPush =
ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
public static final ParcelUuid Hid =
ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb");
public static final ParcelUuid Hogp =
ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb");
public static final ParcelUuid PANU =
ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid NAP =
ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid BNEP =
ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid PBAP_PCE =
ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid PBAP_PSE =
ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MAP =
ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MNS =
ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MAS =
ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid SAP =
ParcelUuid.fromString("0000112D-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid BASE_UUID =
ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
/** Length of bytes for 16 bit UUID */
public static final int UUID_BYTES_16_BIT = 2;
/** Length of bytes for 32 bit UUID */
public static final int UUID_BYTES_32_BIT = 4;
/** Length of bytes for 128 bit UUID */
public static final int UUID_BYTES_128_BIT = 16;
public static final ParcelUuid[] RESERVED_UUIDS = {
AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
ObexObjectPush, PANU, NAP, MAP, MNS, MAS, SAP};
public static boolean isAudioSource(ParcelUuid uuid) {
return uuid.equals(AudioSource);
}
public static boolean isAudioSink(ParcelUuid uuid) {
return uuid.equals(AudioSink);
}
public static boolean isAdvAudioDist(ParcelUuid uuid) {
return uuid.equals(AdvAudioDist);
}
public static boolean isHandsfree(ParcelUuid uuid) {
return uuid.equals(Handsfree);
}
public static boolean isHeadset(ParcelUuid uuid) {
return uuid.equals(HSP);
}
public static boolean isAvrcpController(ParcelUuid uuid) {
return uuid.equals(AvrcpController);
}
public static boolean isAvrcpTarget(ParcelUuid uuid) {
return uuid.equals(AvrcpTarget);
}
public static boolean isInputDevice(ParcelUuid uuid) {
return uuid.equals(Hid);
}
public static boolean isPanu(ParcelUuid uuid) {
return uuid.equals(PANU);
}
public static boolean isNap(ParcelUuid uuid) {
return uuid.equals(NAP);
}
public static boolean isBnep(ParcelUuid uuid) {
return uuid.equals(BNEP);
}
public static boolean isMap(ParcelUuid uuid) {
return uuid.equals(MAP);
}
public static boolean isMns(ParcelUuid uuid) {
return uuid.equals(MNS);
}
public static boolean isMas(ParcelUuid uuid) {
return uuid.equals(MAS);
}
public static boolean isSap(ParcelUuid uuid) {
return uuid.equals(SAP);
}
/**
* Returns true if ParcelUuid is present in uuidArray
*
* #param uuidArray - Array of ParcelUuids
* #param uuid
*/
public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) {
if ((uuidArray == null || uuidArray.length == 0) && uuid == null)
return true;
if (uuidArray == null)
return false;
for (ParcelUuid element: uuidArray) {
if (element.equals(uuid)) return true;
}
return false;
}
/**
* Returns true if there any common ParcelUuids in uuidA and uuidB.
*
* #param uuidA - List of ParcelUuids
* #param uuidB - List of ParcelUuids
*
*/
public static boolean containsAnyUuid(ParcelUuid[] uuidA, ParcelUuid[] uuidB) {
if (uuidA == null && uuidB == null) return true;
if (uuidA == null) {
return uuidB.length == 0 ? true : false;
}
if (uuidB == null) {
return uuidA.length == 0 ? true : false;
}
HashSet<ParcelUuid> uuidSet = new HashSet<ParcelUuid> (Arrays.asList(uuidA));
for (ParcelUuid uuid: uuidB) {
if (uuidSet.contains(uuid)) return true;
}
return false;
}
/**
* Returns true if all the ParcelUuids in ParcelUuidB are present in
* ParcelUuidA
*
* #param uuidA - Array of ParcelUuidsA
* #param uuidB - Array of ParcelUuidsB
*
*/
public static boolean containsAllUuids(ParcelUuid[] uuidA, ParcelUuid[] uuidB) {
if (uuidA == null && uuidB == null) return true;
if (uuidA == null) {
return uuidB.length == 0 ? true : false;
}
if (uuidB == null) return true;
HashSet<ParcelUuid> uuidSet = new HashSet<ParcelUuid> (Arrays.asList(uuidA));
for (ParcelUuid uuid: uuidB) {
if (!uuidSet.contains(uuid)) return false;
}
return true;
}
/**
* Extract the Service Identifier or the actual uuid from the Parcel Uuid.
* For example, if 0000110B-0000-1000-8000-00805F9B34FB is the parcel Uuid,
* this function will return 110B
* #param parcelUuid
* #return the service identifier.
*/
public static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) {
UUID uuid = parcelUuid.getUuid();
long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32;
return (int)value;
}
/**
* Parse UUID from bytes. The {#code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID,
* but the returned UUID is always in 128-bit format.
* Note UUID is little endian in Bluetooth.
*
* #param uuidBytes Byte representation of uuid.
* #return {#link ParcelUuid} parsed from bytes.
* #throws IllegalArgumentException If the {#code uuidBytes} cannot be parsed.
*/
public static ParcelUuid parseUuidFrom(byte[] uuidBytes) {
if (uuidBytes == null) {
throw new IllegalArgumentException("uuidBytes cannot be null");
}
int length = uuidBytes.length;
if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT &&
length != UUID_BYTES_128_BIT) {
throw new IllegalArgumentException("uuidBytes length invalid - " + length);
}
// Construct a 128 bit UUID.
if (length == UUID_BYTES_128_BIT) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
long msb = buf.getLong(8);
long lsb = buf.getLong(0);
return new ParcelUuid(new UUID(msb, lsb));
}
// For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
// 128_bit_value = uuid * 2^96 + BASE_UUID
long shortUuid;
if (length == UUID_BYTES_16_BIT) {
shortUuid = uuidBytes[0] & 0xFF;
shortUuid += (uuidBytes[1] & 0xFF) << 8;
} else {
shortUuid = uuidBytes[0] & 0xFF ;
shortUuid += (uuidBytes[1] & 0xFF) << 8;
shortUuid += (uuidBytes[2] & 0xFF) << 16;
shortUuid += (uuidBytes[3] & 0xFF) << 24;
}
long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32);
long lsb = BASE_UUID.getUuid().getLeastSignificantBits();
return new ParcelUuid(new UUID(msb, lsb));
}
/**
* Parse UUID to bytes. The returned value is shortest representation, a 16-bit, 32-bit or 128-bit UUID,
* Note returned value is little endian (Bluetooth).
*
* #param uuid uuid to parse.
* #return shortest representation of {#code uuid} as bytes.
* #throws IllegalArgumentException If the {#code uuid} is null.
*/
public static byte[] uuidToBytes(ParcelUuid uuid) {
if (uuid == null) {
throw new IllegalArgumentException("uuid cannot be null");
}
if (is16BitUuid(uuid)) {
byte[] uuidBytes = new byte[UUID_BYTES_16_BIT];
int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
uuidBytes[0] = (byte)(uuidVal & 0xFF);
uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
return uuidBytes;
}
if (is32BitUuid(uuid)) {
byte[] uuidBytes = new byte[UUID_BYTES_32_BIT];
int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
uuidBytes[0] = (byte)(uuidVal & 0xFF);
uuidBytes[1] = (byte)((uuidVal & 0xFF00) >> 8);
uuidBytes[2] = (byte)((uuidVal & 0xFF0000) >> 16);
uuidBytes[3] = (byte)((uuidVal & 0xFF000000) >> 24);
return uuidBytes;
}
// Construct a 128 bit UUID.
long msb = uuid.getUuid().getMostSignificantBits();
long lsb = uuid.getUuid().getLeastSignificantBits();
byte[] uuidBytes = new byte[UUID_BYTES_128_BIT];
ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
buf.putLong(8, msb);
buf.putLong(0, lsb);
return uuidBytes;
}
/**
* Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
*
* #param parcelUuid
* #return true if the parcelUuid can be converted to 16 bit uuid, false otherwise.
*/
public static boolean is16BitUuid(ParcelUuid parcelUuid) {
UUID uuid = parcelUuid.getUuid();
if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
return false;
}
return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L);
}
/**
* Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid.
*
* #param parcelUuid
* #return true if the parcelUuid can be converted to 32 bit uuid, false otherwise.
*/
public static boolean is32BitUuid(ParcelUuid parcelUuid) {
UUID uuid = parcelUuid.getUuid();
if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
return false;
}
if (is16BitUuid(parcelUuid)) {
return false;
}
return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L);
}
From my testing the results yield exactly what I want.
You need add service UUID in the advertisement data as the link
Then you can try again startLeScan (UUID[],callback).
I had succeed by this method to discovery thermometer device with specific UUID[0x1809]
It does work for me.
My experience is that I had to supply EVERY service that a device I want to connect to presents, not just the one I am concerned with. I ended up doing service discovery after scan to get around this.
128 bit UUID LE scan worked on Samsung S5, running Android 4.4.2; but yes it fails on Nexus 4, 7. Tested on 4.4.2, 4.4.3, 4.4.4.
I've found a bug in Android source 5.x, but not present in 6.x.
There a function in this file:
http://androidxref.com/5.1.1_r6/xref/external/bluetooth/bluedroid/bta/dm/bta_dm_api.c#1560
used to pass a 32 bit data_mask to bluetooth le advertising and scan response stack.
But the structure "tBTA_DM_API_SET_ADV_CONFIG;" manage 16 bit long value !!!
So change UINT16 to UINT32 dor data_mask, recompile Android and it will works.
Rif
http://androidxref.com/5.1.1_r6/xref/external/bluetooth/bluedroid/bta/dm/bta_dm_int.h#594
I have been running into the same issue with the SensorTag from TI using my N7 2013 with Android 4.3.
What I have found to work is to start the LeScan, wait a second, stop it and then restart it.
if(!mBluetoothAdapter.startLeScan(mBleScanCallback)){
L.e("could not start scan action");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
mBluetoothAdapter.stopLeScan(mBleScanCallback);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
mBluetoothAdapter.startLeScan(mBleScanCallback);
Also I noticed that sometimes the connection is not established (this might or might not be related to the firmware implementation or closing the connection properly). In the same way it seems that trying to reconnect to the gatt seems to do the trick.
Having to use these workarounds is really disappointing...
I am using phonegap with socket programming.
When I check on emulator I am getting VM aborting error.
I am just calling my server and checking it to my emulator.
Here is my code.
package com.example.test;
import org.apache.cordova.DroidGap;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends DroidGap {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
super.loadUrl("file:///android_asset/www/index.html");
appView.addJavascriptInterface(new WebSocketFactory(appView), "WebSocketFactory");
}
}
---------------------------------------------------------------------
package com.example.test;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import android.util.Log;
import android.webkit.WebView;
public class WebSocket implements Runnable {
/**
* Enum for WebSocket Draft
*/
public enum Draft {
DRAFT75, DRAFT76
}
// //////////////// CONSTANT
/**
* The connection has not yet been established.
*/
public final static int WEBSOCKET_STATE_CONNECTING = 0;
/**
* The WebSocket connection is established and communication is possible.
*/
public final static int WEBSOCKET_STATE_OPEN = 1;
/**
* The connection is going through the closing handshake.
*/
public final static int WEBSOCKET_STATE_CLOSING = 2;
/**
* The connection has been closed or could not be opened.
*/
public final static int WEBSOCKET_STATE_CLOSED = 3;
/**
* An empty string
*/
private static String BLANK_MESSAGE = "";
/**
* The javascript method name for onOpen event.
*/
private static String EVENT_ON_OPEN = "onopen";
/**
* The javascript method name for onMessage event.
*/
private static String EVENT_ON_MESSAGE = "onmessage";
/**
* The javascript method name for onClose event.
*/
private static String EVENT_ON_CLOSE = "onclose";
/**
* The javascript method name for onError event.
*/
private static String EVENT_ON_ERROR = "onerror";
/**
* The default port of WebSockets, as defined in the spec.
*/
public static final int DEFAULT_PORT = 80;
/**
* The WebSocket protocol expects UTF-8 encoded bytes.
*/
public static final String UTF8_CHARSET = "UTF-8";
/**
* The byte representing Carriage Return, or \r
*/
public static final byte DATA_CR = (byte) 0x0D;
/**
* The byte representing Line Feed, or \n
*/
public static final byte DATA_LF = (byte) 0x0A;
/**
* The byte representing the beginning of a WebSocket text frame.
*/
public static final byte DATA_START_OF_FRAME = (byte) 0x00;
/**
* The byte representing the end of a WebSocket text frame.
*/
public static final byte DATA_END_OF_FRAME = (byte) 0xFF;
// //////////////// INSTANCE Variables
/**
* The WebView instance from Phonegap DroidGap
*/
private final WebView appView;
/**
* The unique id for this instance (helps to bind this to javascript events)
*/
private String id;
/**
* The URI this client is supposed to connect to.
*/
private URI uri;
/**
* The port of the websocket server
*/
private int port;
/**
* The Draft of the WebSocket protocol the Client is adhering to.
*/
private Draft draft;
/**
* The <tt>SocketChannel</tt> instance to use for this server connection.
* This is used to read and write data to.
*/
private SocketChannel socketChannel;
/**
* The 'Selector' used to get event keys from the underlying socket.
*/
private Selector selector;
/**
* Keeps track of whether or not the client thread should continue running.
*/
private boolean running;
/**
* Internally used to determine whether to recieve data as part of the
* remote handshake, or as part of a text frame.
*/
private boolean handshakeComplete;
/**
* The 1-byte buffer reused throughout the WebSocket connection to read
* data.
*/
private ByteBuffer buffer;
/**
* The bytes that make up the remote handshake.
*/
private ByteBuffer remoteHandshake;
/**
* The bytes that make up the current text frame being read.
*/
private ByteBuffer currentFrame;
/**
* Queue of buffers that need to be sent to the client.
*/
private BlockingQueue<ByteBuffer> bufferQueue;
/**
* Lock object to ensure that data is sent from the bufferQueue in the
* proper order
*/
private Object bufferQueueMutex = new Object();
/**
* Number 1 used in handshake
*/
private int number1 = 0;
/**
* Number 2 used in handshake
*/
private int number2 = 0;
/**
* Key3 used in handshake
*/
private byte[] key3 = null;
/**
* The readyState attribute represents the state of the connection.
*/
private int readyState = WEBSOCKET_STATE_CONNECTING;
private final WebSocket instance;
/**
* Constructor.
*
* Note: this is protected because it's supposed to be instantiated from {#link WebSocketFactory} only.
*
* #param appView
* {#link android.webkit.WebView}
* #param uri
* websocket server {#link URI}
* #param draft
* websocket server {#link Draft} implementation (75/76)
* #param id
* unique id for this instance
*/
protected WebSocket(WebView appView, URI uri, Draft draft, String id) {
this.appView = appView;
this.uri = uri;
this.draft = draft;
// port
port = uri.getPort();
if (port == -1) {
port = DEFAULT_PORT;
}
// Id
this.id = id;
this.bufferQueue = new LinkedBlockingQueue<ByteBuffer>();
this.handshakeComplete = false;
this.remoteHandshake = this.currentFrame = null;
this.buffer = ByteBuffer.allocate(1);
this.instance = this;
}
// //////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////// WEB SOCKET API Methods
// ///////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////
/**
* Starts a new Thread and connects to server
*
* #throws IOException
*/
public Thread connect() throws IOException {
this.running = true;
this.readyState = WEBSOCKET_STATE_CONNECTING;
// open socket
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
// set address
socketChannel.connect(new InetSocketAddress(uri.getHost(), port));
// start a thread to make connection
// More info:
// http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82
// http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
Log.v("websocket", "Starting a new thread to manage data reading/writing");
Thread th = new Thread(this);
th.start();
// return thread object for explicit closing, if needed
return th;
}
public void run() {
while (this.running) {
try {
_connect();
} catch (IOException e) {
this.onError(e);
}
}
}
/**
* Closes connection with server
*/
public void close() {
this.readyState = WebSocket.WEBSOCKET_STATE_CLOSING;
// close socket channel
try {
this.socketChannel.close();
} catch (IOException e) {
this.onError(e);
}
this.running = false;
selector.wakeup();
// fire onClose method
this.onClose();
this.readyState = WebSocket.WEBSOCKET_STATE_CLOSED;
}
/**
* Sends <var>text</var> to server
*
* #param text
* String to send to server
*/
public void send(final String text) {
new Thread(new Runnable() {
#Override
public void run() {
if (instance.readyState == WEBSOCKET_STATE_OPEN) {
try {
instance._send(text);
} catch (IOException e) {
instance.onError(e);
}
} else {
instance.onError(new NotYetConnectedException());
}
}
}).start();
}
/**
* Called when an entire text frame has been received.
*
* #param msg
* Message from websocket server
*/
public void onMessage(final String msg) {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_MESSAGE, msg));
}
});
}
public void onOpen() {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_OPEN, BLANK_MESSAGE));
}
});
}
public void onClose() {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_CLOSE, BLANK_MESSAGE));
}
});
}
public void onError(final Throwable t) {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_ERROR, t.getMessage()));
}
});
}
public String getId() {
return id;
}
/**
* #return the readyState
*/
public int getReadyState() {
return readyState;
}
/**
* Builds text for javascript engine to invoke proper event method with
* proper data.
*
* #param event
* websocket event (onOpen, onMessage etc.)
* #param msg
* Text message received from websocket server
* #return
*/
private String buildJavaScriptData(String event, String msg) {
String _d = "javascript:WebSocket." + event + "(" + "{" + "\"_target\":\"" + id + "\"," + "\"data\":'" + msg.replaceAll("'", "\\\\'")
+ "'" + "}" + ")";
return _d;
}
// //////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////// WEB SOCKET Internal Methods
// //////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////
private boolean _send(String text) throws IOException {
if (!this.handshakeComplete) {
throw new NotYetConnectedException();
}
if (text == null) {
throw new NullPointerException("Cannot send 'null' data to a WebSocket.");
}
// Get 'text' into a WebSocket "frame" of bytes
byte[] textBytes = text.getBytes(UTF8_CHARSET.toString());
ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2);
b.put(DATA_START_OF_FRAME);
b.put(textBytes);
b.put(DATA_END_OF_FRAME);
b.rewind();
// See if we have any backlog that needs to be sent first
if (_write()) {
// Write the ByteBuffer to the socket
this.socketChannel.write(b);
}
// If we didn't get it all sent, add it to the buffer of buffers
if (b.remaining() > 0) {
if (!this.bufferQueue.offer(b)) {
throw new IOException("Buffers are full, message could not be sent to"
+ this.socketChannel.socket().getRemoteSocketAddress());
}
return false;
}
return true;
}
// actual connection logic
private void _connect() throws IOException {
// Continuous loop that is only supposed to end when "close" is called.
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> i = keys.iterator();
while (i.hasNext()) {
SelectionKey key = i.next();
i.remove();
if (key.isConnectable()) {
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
socketChannel.register(selector, SelectionKey.OP_READ);
_writeHandshake();
}
if (key.isReadable()) {
try {
_read();
} catch (NoSuchAlgorithmException nsa) {
this.onError(nsa);
}
}
}
}
private void _writeHandshake() throws IOException {
String path = this.uri.getPath();
if (path.indexOf("/") != 0) {
path = "/" + path;
}
String host = uri.getHost() + (port != DEFAULT_PORT ? ":" + port : "");
String origin = "*"; // TODO: Make 'origin' configurable
String request = "GET " + path + " HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n"
+ "Host: " + host + "\r\n" + "Origin: " + origin + "\r\n";
// Add random keys for Draft76
if (this.draft == Draft.DRAFT76) {
request += "Sec-WebSocket-Key1: " + this._randomKey() + "\r\n";
request += "Sec-WebSocket-Key2: " + this._randomKey() + "\r\n";
request += "\r\n";
this.key3 = new byte[8];
(new Random()).nextBytes(this.key3);
// Convert to bytes early so last eight bytes don't get jacked
byte[] bRequest = request.getBytes(UTF8_CHARSET);
byte[] bToSend = new byte[bRequest.length + 8];
// Copy in the Request bytes
System.arraycopy(bRequest, 0, bToSend, 0, bRequest.length);
// Now tack on key3 bytes
System.arraycopy(this.key3, 0, bToSend, bRequest.length, this.key3.length);
// Now we can send all keys as a single frame
_write(bToSend);
return;
}
request += "\r\n";
_write(request.getBytes(UTF8_CHARSET));
}
private boolean _write() throws IOException {
synchronized (this.bufferQueueMutex) {
ByteBuffer buffer = this.bufferQueue.peek();
while (buffer != null) {
this.socketChannel.write(buffer);
if (buffer.remaining() > 0) {
return false; // Didn't finish this buffer. There's more to
// send.
} else {
this.bufferQueue.poll(); // Buffer finished. Remove it.
buffer = this.bufferQueue.peek();
}
}
return true;
}
}
private void _write(byte[] bytes) throws IOException {
this.socketChannel.write(ByteBuffer.wrap(bytes));
}
private void _read() throws IOException, NoSuchAlgorithmException {
this.buffer.rewind();
int bytesRead = -1;
try {
bytesRead = this.socketChannel.read(this.buffer);
} catch (Exception ex) {
}
if (bytesRead == -1) {
close();
} else if (bytesRead > 0) {
this.buffer.rewind();
if (!this.handshakeComplete) {
_readHandshake();
} else {
_readFrame();
}
}
}
private void _readFrame() throws UnsupportedEncodingException {
byte newestByte = this.buffer.get();
if (newestByte == DATA_START_OF_FRAME) { // Beginning of Frame
this.currentFrame = null;
} else if (newestByte == DATA_END_OF_FRAME) { // End of Frame
String textFrame = null;
// currentFrame will be null if END_OF_FRAME was send directly after
// START_OF_FRAME, thus we will send 'null' as the sent message.
if (this.currentFrame != null) {
textFrame = new String(this.currentFrame.array(), "ISO-8859-1");
}
// fire onMessage method
this.onMessage(textFrame);
} else { // Regular frame data, add to current frame buffer
ByteBuffer frame = ByteBuffer.allocate((this.currentFrame != null ? this.currentFrame.capacity() : 0)
+ this.buffer.capacity());
if (this.currentFrame != null) {
this.currentFrame.rewind();
frame.put(this.currentFrame);
}
frame.put(newestByte);
this.currentFrame = frame;
String textFrame = new String(this.currentFrame.array(),"ISO-8859-1");
this.onMessage(textFrame);
}
}
private void _readHandshake() throws IOException, NoSuchAlgorithmException {
ByteBuffer ch = ByteBuffer.allocate((this.remoteHandshake != null ? this.remoteHandshake.capacity() : 0)
+ this.buffer.capacity());
if (this.remoteHandshake != null) {
this.remoteHandshake.rewind();
ch.put(this.remoteHandshake);
}
ch.put(this.buffer);
this.remoteHandshake = ch;
byte[] h = this.remoteHandshake.array();
// If the ByteBuffer contains 16 random bytes, and ends with
// 0x0D 0x0A 0x0D 0x0A (or two CRLFs), then the client
// handshake is complete for Draft 76 Client.
if ((h.length >= 20 && h[h.length - 20] == DATA_CR && h[h.length - 19] == DATA_LF
&& h[h.length - 18] == DATA_CR && h[h.length - 17] == DATA_LF)) {
_readHandshake(new byte[] { h[h.length - 16], h[h.length - 15], h[h.length - 14], h[h.length - 13],
h[h.length - 12], h[h.length - 11], h[h.length - 10], h[h.length - 9], h[h.length - 8],
h[h.length - 7], h[h.length - 6], h[h.length - 5], h[h.length - 4], h[h.length - 3],
h[h.length - 2], h[h.length - 1] });
// If the ByteBuffer contains 8 random bytes,ends with
// 0x0D 0x0A 0x0D 0x0A (or two CRLFs), and the response
// contains Sec-WebSocket-Key1 then the client
// handshake is complete for Draft 76 Server.
} else if ((h.length >= 12 && h[h.length - 12] == DATA_CR && h[h.length - 11] == DATA_LF
&& h[h.length - 10] == DATA_CR && h[h.length - 9] == DATA_LF)
&& new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec-WebSocket-Key1")) {// ************************
_readHandshake(new byte[] { h[h.length - 8], h[h.length - 7], h[h.length - 6], h[h.length - 5],
h[h.length - 4], h[h.length - 3], h[h.length - 2], h[h.length - 1] });
// Consider Draft 75, and the Flash Security Policy
// Request edge-case.
} else if ((h.length >= 4 && h[h.length - 4] == DATA_CR && h[h.length - 3] == DATA_LF
&& h[h.length - 2] == DATA_CR && h[h.length - 1] == DATA_LF)
&& !(new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec"))
|| (h.length == 23 && h[h.length - 1] == 0)) {
_readHandshake(null);
}else{
_readHandshake(null);
}
}
private void _readHandshake(byte[] handShakeBody) throws IOException, NoSuchAlgorithmException {
// byte[] handshakeBytes = this.remoteHandshake.array();
// String handshake = new String(handshakeBytes, UTF8_CHARSET);
// TODO: Do some parsing of the returned handshake, and close connection
// in received anything unexpected!
this.handshakeComplete = true;
boolean isConnectionReady = true;
if (this.draft == WebSocket.Draft.DRAFT76) {
if (handShakeBody == null) {
isConnectionReady = true;
} else{
byte[] challenge = new byte[] { (byte) (this.number1 >> 24), (byte) ((this.number1 << 8) >> 24),
(byte) ((this.number1 << 16) >> 24), (byte) ((this.number1 << 24) >> 24),
(byte) (this.number2 >> 24), (byte) ((this.number2 << 8) >> 24),
(byte) ((this.number2 << 16) >> 24), (byte) ((this.number2 << 24) >> 24), this.key3[0],
this.key3[1], this.key3[2], this.key3[3], this.key3[4], this.key3[5], this.key3[6], this.key3[7] };
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] expected = md5.digest(challenge);
for (int i = 0; i < handShakeBody.length; i++) {
if (expected[i] != handShakeBody[i]) {
isConnectionReady = true;
}
}
}
}
if (isConnectionReady) {
this.readyState = WEBSOCKET_STATE_OPEN;
// fire onOpen method
this.onOpen();
} else {
close();
}
}
private String _randomKey() {
Random r = new Random();
long maxNumber = 4294967295L;
long spaces = r.nextInt(12) + 1;
int max = new Long(maxNumber / spaces).intValue();
max = Math.abs(max);
int number = r.nextInt(max) + 1;
if (this.number1 == 0) {
this.number1 = number;
} else {
this.number2 = number;
}
long product = number * spaces;
String key = Long.toString(product);
int numChars = r.nextInt(12);
for (int i = 0; i < numChars; i++) {
int position = r.nextInt(key.length());
position = Math.abs(position);
char randChar = (char) (r.nextInt(95) + 33);
// exclude numbers here
if (randChar >= 48 && randChar <= 57) {
randChar -= 15;
}
key = new StringBuilder(key).insert(position, randChar).toString();
}
for (int i = 0; i < spaces; i++) {
int position = r.nextInt(key.length() - 1) + 1;
position = Math.abs(position);
key = new StringBuilder(key).insert(position, "\u0020").toString();
}
return key;
}
}
----------------------------------------------------------------------------
package com.example.test;
import java.net.URI;
import java.util.Random;
import android.webkit.WebView;
/**
* The <tt>WebSocketFactory</tt> is like a helper class to instantiate new
* WebSocket instaces especially from Javascript side. It expects a valid
* "ws://" URI.
*
* #author Animesh Kumar
*/
public class WebSocketFactory {
/** The app view. */
WebView appView;
/**
* Instantiates a new web socket factory.
*
* #param appView
* the app view
*/
public WebSocketFactory(WebView appView) {
this.appView = appView;
}
public WebSocket getInstance(String url) {
// use Draft75 by default
return getInstance(url, WebSocket.Draft.DRAFT76);
}
public WebSocket getInstance(String url, WebSocket.Draft draft) {
WebSocket socket = null;
Thread th = null;
try {
socket = new WebSocket(appView, new URI(url), draft, getRandonUniqueId());
th = socket.connect();
return socket;
} catch (Exception e) {
//Log.v("websocket", e.toString());
if(th != null) {
th.interrupt();
}
}
return null;
}
/**
* Generates random unique ids for WebSocket instances
*
* #return String
*/
private String getRandonUniqueId() {
return "WEBSOCKET." + new Random().nextInt(100);
}
}
-------------------------------------------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>Hello World</title>
</head>
<body>
<script type="text/javascript" src="cordova-2.7.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript" src="js/websocket.js"></script>
<script type="text/javascript">
var socket = new WebSocket('ws://192.168.1.3:8101/');
// push a message after the connection is established.
socket.onopen = function() {
socket.send('Hello World')
};
// alerts message pushed from server
socket.onmessage = function(msg) {
console.log("TTTTT"+JSON.stringify(msg));
};
// alert close event
socket.onclose = function() {
alert('closed');
};
</script>
</body>
</html>
From the script I am calling my functions.
But when I check it on emulator it is showing that error.
Please help me removing this error.
Thanks.
I'm a newbie in android programming. So, basically I was trying to automate a file transfer from my android phone(2.3.3) to a PAIRED laptop bluetooth...
I can send manually using share--bluetooth--search for paired device and then send it.
But I want to accomplish this automatically.
APP must only be on sender side. (Duh! receiver is laptop)
App code:
File myFile = new File(Environment.getExternalStorageDirectory()+"/mysdfile.txt");
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null){}
if(!mBluetoothAdapter.isEnabled()){
Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetooth, 0);
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if(pairedDevices.size() > 0){
for(BluetoothDevice device : pairedDevices){
if(device.getName().equals("HARSHAR-HP")){
mmDevice = device;
mdeviceadd= device.getAddress();
Log.d(TAG, "found device");
break;
}else {
Log.d(TAG, "FAilure");
}
}
}
ContentValues values = new ContentValues();
values.put(BluetoothShare.URI,Environment.getExternalStorageDirectory()+"/mysdfile.txt");
values.put(BluetoothShare.DESTINATION, mdeviceadd);
values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_OUTBOUND);
Long ts = System.currentTimeMillis();
values.put(BluetoothShare.TIMESTAMP, ts);
getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
Log.d(TAG, "Till here ok");
BluetoothShare.java:
import android.provider.BaseColumns;
import android.net.Uri;
/**
* Exposes constants used to interact with the Bluetooth Share manager's content
* provider.
*/
public final class BluetoothShare implements BaseColumns {
private BluetoothShare() {
}
/**
* The permission to access the Bluetooth Share Manager
*/
public static final String PERMISSION_ACCESS = "android.permission.ACCESS_BLUETOOTH_SHARE";
/**
* The content:// URI for the data table in the provider
*/
public static final Uri CONTENT_URI = Uri.parse("content://com.android.bluetooth.opp/btopp");
/**
* Broadcast Action: this is sent by the Bluetooth Share component to
* transfer complete. The request detail could be retrieved by app * as _ID
* is specified in the intent's data.
*/
public static final String TRANSFER_COMPLETED_ACTION = "android.btopp.intent.action.TRANSFER_COMPLETE";
/**
* This is sent by the Bluetooth Share component to indicate there is an
* incoming file need user to confirm.
*/
public static final String INCOMING_FILE_CONFIRMATION_REQUEST_ACTION = "android.btopp.intent.action.INCOMING_FILE_NOTIFICATION";
/**
* This is sent by the Bluetooth Share component to indicate there is an
* incoming file request timeout and need update UI.
*/
public static final String USER_CONFIRMATION_TIMEOUT_ACTION = "android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT";
/**
* The name of the column containing the URI of the file being
* sent/received.
*/
public static final String URI = "uri";
/**
* The name of the column containing the filename that the incoming file
* request recommends. When possible, the Bluetooth Share manager will
* attempt to use this filename, or a variation, as the actual name for the
* file.
*/
public static final String FILENAME_HINT = "hint";
/**
* The name of the column containing the filename where the shared file was
* actually stored.
*/
public static final String _DATA = "_data";
/**
* The name of the column containing the MIME type of the shared file.
*/
public static final String MIMETYPE = "mimetype";
/**
* The name of the column containing the direction (Inbound/Outbound) of the
* transfer. See the DIRECTION_* constants for a list of legal values.
*/
public static final String DIRECTION = "direction";
/**
* The name of the column containing Bluetooth Device Address that the
* transfer is associated with.
*/
public static final String DESTINATION = "destination";
/**
* The name of the column containing the flags that controls whether the
* transfer is displayed by the UI. See the VISIBILITY_* constants for a
* list of legal values.
*/
public static final String VISIBILITY = "visibility";
/**
* The name of the column containing the current user confirmation state of
* the transfer. Applications can write to this to confirm the transfer. the
* USER_CONFIRMATION_* constants for a list of legal values.
*/
public static final String USER_CONFIRMATION = "confirm";
/**
* The name of the column containing the current status of the transfer.
* Applications can read this to follow the progress of each download. See
* the STATUS_* constants for a list of legal values.
*/
public static final String STATUS = "status";
/**
* The name of the column containing the total size of the file being
* transferred.
*/
public static final String TOTAL_BYTES = "total_bytes";
/**
* The name of the column containing the size of the part of the file that
* has been transferred so far.
*/
public static final String CURRENT_BYTES = "current_bytes";
/**
* The name of the column containing the timestamp when the transfer is
* initialized.
*/
public static final String TIMESTAMP = "timestamp";
/**
* This transfer is outbound, e.g. share file to other device.
*/
public static final int DIRECTION_OUTBOUND = 0;
/**
* This transfer is inbound, e.g. receive file from other device.
*/
public static final int DIRECTION_INBOUND = 1;
/**
* This transfer is waiting for user confirmation.
*/
public static final int USER_CONFIRMATION_PENDING = 0;
/**
* This transfer is confirmed by user.
*/
public static final int USER_CONFIRMATION_CONFIRMED = 1;
/**
* This transfer is auto-confirmed per previous user confirmation.
*/
public static final int USER_CONFIRMATION_AUTO_CONFIRMED = 2;
/**
* This transfer is denied by user.
*/
public static final int USER_CONFIRMATION_DENIED = 3;
/**
* This transfer is timeout before user action.
*/
public static final int USER_CONFIRMATION_TIMEOUT = 4;
/**
* This transfer is visible and shows in the notifications while in progress
* and after completion.
*/
public static final int VISIBILITY_VISIBLE = 0;
/**
* This transfer doesn't show in the notifications.
*/
public static final int VISIBILITY_HIDDEN = 1;
/**
* Returns whether the status is informational (i.e. 1xx).
*/
public static boolean isStatusInformational(int status) {
return (status >= 100 && status < 200);
}
/**
* Returns whether the transfer is suspended. (i.e. whether the transfer
* won't complete without some action from outside the transfer manager).
*/
public static boolean isStatusSuspended(int status) {
return (status == STATUS_PENDING);
}
/**
* Returns whether the status is a success (i.e. 2xx).
*/
public static boolean isStatusSuccess(int status) {
return (status >= 200 && status < 300);
}
/**
* Returns whether the status is an error (i.e. 4xx or 5xx).
*/
public static boolean isStatusError(int status) {
return (status >= 400 && status < 600);
}
/**
* Returns whether the status is a client error (i.e. 4xx).
*/
public static boolean isStatusClientError(int status) {
return (status >= 400 && status < 500);
}
/**
* Returns whether the status is a server error (i.e. 5xx).
*/
public static boolean isStatusServerError(int status) {
return (status >= 500 && status < 600);
}
/**
* Returns whether the transfer has completed (either with success or
* error).
*/
public static boolean isStatusCompleted(int status) {
return (status >= 200 && status < 300) || (status >= 400 && status < 600);
}
/**
* This transfer hasn't stated yet
*/
public static final int STATUS_PENDING = 190;
/**
* This transfer has started
*/
public static final int STATUS_RUNNING = 192;
/**
* This transfer has successfully completed. Warning: there might be other
* status values that indicate success in the future. Use isSucccess() to
* capture the entire category.
*/
public static final int STATUS_SUCCESS = 200;
/**
* This request couldn't be parsed. This is also used when processing
* requests with unknown/unsupported URI schemes.
*/
public static final int STATUS_BAD_REQUEST = 400;
/**
* This transfer is forbidden by target device.
*/
public static final int STATUS_FORBIDDEN = 403;
/**
* This transfer can't be performed because the content cannot be handled.
*/
public static final int STATUS_NOT_ACCEPTABLE = 406;
/**
* This transfer cannot be performed because the length cannot be determined
* accurately. This is the code for the HTTP error "Length Required", which
* is typically used when making requests that require a content length but
* don't have one, and it is also used in the client when a response is
* received whose length cannot be determined accurately (therefore making
* it impossible to know when a transfer completes).
*/
public static final int STATUS_LENGTH_REQUIRED = 411;
/**
* This transfer was interrupted and cannot be resumed. This is the code for
* the OBEX error "Precondition Failed", and it is also used in situations
* where the client doesn't have an ETag at all.
*/
public static final int STATUS_PRECONDITION_FAILED = 412;
/**
* This transfer was canceled
*/
public static final int STATUS_CANCELED = 490;
/**
* This transfer has completed with an error. Warning: there will be other
* status values that indicate errors in the future. Use isStatusError() to
* capture the entire category.
*/
public static final int STATUS_UNKNOWN_ERROR = 491;
/**
* This transfer couldn't be completed because of a storage issue.
* Typically, that's because the file system is missing or full.
*/
public static final int STATUS_FILE_ERROR = 492;
/**
* This transfer couldn't be completed because of no sdcard.
*/
public static final int STATUS_ERROR_NO_SDCARD = 493;
/**
* This transfer couldn't be completed because of sdcard full.
*/
public static final int STATUS_ERROR_SDCARD_FULL = 494;
/**
* This transfer couldn't be completed because of an unspecified un-handled
* OBEX code.
*/
public static final int STATUS_UNHANDLED_OBEX_CODE = 495;
/**
* This transfer couldn't be completed because of an error receiving or
* processing data at the OBEX level.
*/
public static final int STATUS_OBEX_DATA_ERROR = 496;
/**
* This transfer couldn't be completed because of an error when establishing
* connection.
*/
public static final int STATUS_CONNECTION_ERROR = 497;
}
NO error in code... but nothing happens.
file is not transfered.. please someone help me fix the code.. DOnt ask me to see SAMPLE EXAMPLE, Didnt understand how to send file through it.
I'm afraid that way of transferring files is not valid in Android 4+.
Instead, you can use this piece of code, though it will not transfer automatically the file, it prompts the user to select the destination. I don't think it's possible to send files without user authorization anymore, it's a huge security flaw in my opinion.
public void sendFile(String fileName){
Log.d(TAG, "Sending file...");
File dir = Environment.getExternalStorageDirectory();
File manualFile = new File(dir, "/" + fileName);
Uri uri = Uri.fromFile(manualFile);
String type = "application/pdf";
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType(type);
sharingIntent.setClassName("com.android.bluetooth", "com.android.bluetooth.opp.BluetoothOppLauncherActivity");
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(sharingIntent);
}
I'm trying to get the directions from the user's current location to a user defined location in the app that I'm building. This seems like it should be a relatively easy thing to do but I'm getting stuck on which API to use.
Right now I've managed to hook up to the google directions API but the JSON it's returning is very strange (they've added a \n everywhere to make it human readable) which suggests to me that this API isn't the correct one for mobile.
I've also seen the google places API being recommended for this purpose but I can't seem to find out how to use it from the documentation.
Any help is greatly appreciated since I'm just a titch confused
EDIT: Just to clarify the questions is which API I should be using to get directions for maps?
I do not remember where I get it but this classes help me a lot, it includes if you want to draw the route.
GoogleParser.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.android.maps.GeoPoint;
import com.monumentos.log.Logger;
public class GoogleParser extends XMLParser implements Parser {
/** Distance covered. **/
private int distance;
public GoogleParser(String feedUrl) {
super(feedUrl);
}
/**
* Parses a url pointing to a Google JSON object to a Route object.
* #return a Route object based on the JSON object.
*/
public Route parse() {
// turn the stream into a string
final String result = convertStreamToString(this.getInputStream());
//Create an empty route
final Route route = new Route();
//Create an empty segment
final Segment segment = new Segment();
try {
//Tranform the string into a json object
final JSONObject json = new JSONObject(result);
//Get the route object
final JSONObject jsonRoute = json.getJSONArray("routes").getJSONObject(0);
//Get the leg, only one leg as we don't support waypoints
final JSONObject leg = jsonRoute.getJSONArray("legs").getJSONObject(0);
//Get the steps for this leg
final JSONArray steps = leg.getJSONArray("steps");
//Number of steps for use in for loop
final int numSteps = steps.length();
//Set the name of this route using the start & end addresses
route.setName(leg.getString("start_address") + " to " + leg.getString("end_address"));
//Get google's copyright notice (tos requirement)
route.setCopyright(jsonRoute.getString("copyrights"));
//Get the total length of the route.
route.setLength(leg.getJSONObject("distance").getInt("value"));
//Get any warnings provided (tos requirement)
if (!jsonRoute.getJSONArray("warnings").isNull(0)) {
route.setWarning(jsonRoute.getJSONArray("warnings").getString(0));
}
/* Loop through the steps, creating a segment for each one and
* decoding any polylines found as we go to add to the route object's
* map array. Using an explicit for loop because it is faster!
*/
for (int i = 0; i < numSteps; i++) {
//Get the individual step
final JSONObject step = steps.getJSONObject(i);
//Get the start position for this step and set it on the segment
final JSONObject start = step.getJSONObject("start_location");
final GeoPoint position = new GeoPoint((int) (start.getDouble("lat")*1E6),
(int) (start.getDouble("lng")*1E6));
segment.setPoint(position);
//Set the length of this segment in metres
final int length = step.getJSONObject("distance").getInt("value");
distance += length;
segment.setLength(length);
segment.setDistance(distance/1000);
//Strip html from google directions and set as turn instruction
segment.setInstruction(step.getString("html_instructions").replaceAll("<(.*?)*>", ""));
//Retrieve & decode this segment's polyline and add it to the route.
route.addPoints(decodePolyLine(step.getJSONObject("polyline").getString("points")));
//Push a copy of the segment to the route
route.addSegment(segment.copy());
}
} catch (JSONException e) {
Logger.appendLog( "Google JSON Parser - " + feedUrl);
}
return route;
}
/**
* Convert an inputstream to a string.
* #param input inputstream to convert.
* #return a String of the inputstream.
*/
private static String convertStreamToString(final InputStream input) {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
final StringBuilder sBuf = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sBuf.append(line);
}
} catch (IOException e) {
Logger.appendLog( "Google parser, stream2string");
} finally {
try {
input.close();
} catch (IOException e) {
Logger.appendLog( "Google parser, stream2string");
}
}
return sBuf.toString();
}
/**
* Decode a polyline string into a list of GeoPoints.
* #param poly polyline encoded string to decode.
* #return the list of GeoPoints represented by this polystring.
*/
private List<GeoPoint> decodePolyLine(final String poly) {
int len = poly.length();
int index = 0;
List<GeoPoint> decoded = new ArrayList<GeoPoint>();
int lat = 0;
int lng = 0;
while (index < len) {
int b;
int shift = 0;
int result = 0;
do {
b = poly.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = poly.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
decoded.add(new GeoPoint(
(int) (lat*1E6 / 1E5), (int) (lng*1E6 / 1E5)));
}
return decoded;
}
}
Parser.java
public interface Parser {
public Route parse();
}
Route.java
import java.util.ArrayList;
import java.util.List;
import com.google.android.maps.GeoPoint;
public class Route {
private String name;
private final List<GeoPoint> points;
private List<Segment> segments;
private String copyright;
private String warning;
private String country;
private int length;
private String polyline;
public Route() {
points = new ArrayList<GeoPoint>();
segments = new ArrayList<Segment>();
}
public void addPoint(final GeoPoint p) {
points.add(p);
}
public void addPoints(final List<GeoPoint> points) {
this.points.addAll(points);
}
public List<GeoPoint> getPoints() {
return points;
}
public void addSegment(final Segment s) {
segments.add(s);
}
public List<Segment> getSegments() {
return segments;
}
/**
* #param name the name to set
*/
public void setName(final String name) {
this.name = name;
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param copyright the copyright to set
*/
public void setCopyright(String copyright) {
this.copyright = copyright;
}
/**
* #return the copyright
*/
public String getCopyright() {
return copyright;
}
/**
* #param warning the warning to set
*/
public void setWarning(String warning) {
this.warning = warning;
}
/**
* #return the warning
*/
public String getWarning() {
return warning;
}
/**
* #param country the country to set
*/
public void setCountry(String country) {
this.country = country;
}
/**
* #return the country
*/
public String getCountry() {
return country;
}
/**
* #param length the length to set
*/
public void setLength(int length) {
this.length = length;
}
/**
* #return the length
*/
public int getLength() {
return length;
}
/**
* #param polyline the polyline to set
*/
public void setPolyline(String polyline) {
this.polyline = polyline;
}
/**
* #return the polyline
*/
public String getPolyline() {
return polyline;
}
}
RouteOverlay.java
public class RouteOverlay extends Overlay {
/** GeoPoints representing this routePoints. **/
private final List<GeoPoint> routePoints;
/** Colour to paint routePoints. **/
private int colour;
/** Alpha setting for route overlay. **/
private static final int ALPHA = 200;
/** Stroke width. **/
private static final float STROKE = 4.5f;
/** Route path. **/
private final Path path;
/** Point to draw with. **/
private final Point p;
/** Paint for path. **/
private final Paint paint;
/**
* Public constructor.
*
* #param route Route object representing the route.
* #param defaultColour default colour to draw route in.
*/
public RouteOverlay(final Route route, final int defaultColour) {
super();
routePoints = route.getPoints();
colour = defaultColour;
path = new Path();
p = new Point();
paint = new Paint();
}
#Override
public final void draw(final Canvas c, final MapView mv,
final boolean shadow) {
super.draw(c, mv, shadow);
paint.setColor(colour);
paint.setAlpha(ALPHA);
paint.setAntiAlias(true);
paint.setStrokeWidth(STROKE);
paint.setStyle(Paint.Style.STROKE);
redrawPath(mv);
c.drawPath(path, paint);
}
/**
* Set the colour to draw this route's overlay with.
*
* #param c Int representing colour.
*/
public final void setColour(final int c) {
colour = c;
}
/**
* Clear the route overlay.
*/
public final void clear() {
routePoints.clear();
}
/**
* Recalculate the path accounting for changes to
* the projection and routePoints.
* #param mv MapView the path is drawn to.
*/
private void redrawPath(final MapView mv) {
final Projection prj = mv.getProjection();
path.rewind();
final Iterator<GeoPoint> it = routePoints.iterator();
prj.toPixels(it.next(), p);
path.moveTo(p.x, p.y);
while (it.hasNext()) {
prj.toPixels(it.next(), p);
path.lineTo(p.x, p.y);
}
path.setLastPoint(p.x, p.y);
}
}
Segment.java
import com.google.android.maps.GeoPoint;
public class Segment {
/** Points in this segment. **/
private GeoPoint start;
/** Turn instruction to reach next segment. **/
private String instruction;
/** Length of segment. **/
private int length;
/** Distance covered. **/
private double distance;
/**
* Create an empty segment.
*/
public Segment() {
}
/**
* Set the turn instruction.
* #param turn Turn instruction string.
*/
public void setInstruction(final String turn) {
this.instruction = turn;
}
/**
* Get the turn instruction to reach next segment.
* #return a String of the turn instruction.
*/
public String getInstruction() {
return instruction;
}
/**
* Add a point to this segment.
* #param point GeoPoint to add.
*/
public void setPoint(final GeoPoint point) {
start = point;
}
/** Get the starting point of this
* segment.
* #return a GeoPoint
*/
public GeoPoint startPoint() {
return start;
}
/** Creates a segment which is a copy of this one.
* #return a Segment that is a copy of this one.
*/
public Segment copy() {
final Segment copy = new Segment();
copy.start = start;
copy.instruction = instruction;
copy.length = length;
copy.distance = distance;
return copy;
}
/**
* #param length the length to set
*/
public void setLength(final int length) {
this.length = length;
}
/**
* #return the length
*/
public int getLength() {
return length;
}
/**
* #param distance the distance to set
*/
public void setDistance(double distance) {
this.distance = distance;
}
/**
* #return the distance
*/
public double getDistance() {
return distance;
}
}
XMLParser.java
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import com.monumentos.log.Logger;
public class XMLParser {
// names of the XML tags
protected static final String MARKERS = "markers";
protected static final String MARKER = "marker";
protected URL feedUrl;
protected XMLParser(final String feedUrl) {
try {
this.feedUrl = new URL(feedUrl);
} catch (MalformedURLException e) {
Logger.appendLog( "XML parser - " + feedUrl);
}
}
protected InputStream getInputStream() {
try {
return feedUrl.openConnection().getInputStream();
} catch (IOException e) {
Logger.appendLog( "XML parser - " + feedUrl);
return null;
}
}
}
Hope it will helps you.
Calling api example:
Parser parser;
String jsonURL = "http://maps.googleapis.com/maps/api/directions/json?";
final StringBuffer sBuf = new StringBuffer(jsonURL);
sBuf.append("origin=");
sBuf.append( points.get(0).getLatitudeE6()/1E6);
sBuf.append(',');
sBuf.append(points.get(0).getLongitudeE6()/1E6);
sBuf.append("&destination=");
sBuf.append(points.get(points.size()-1).getLatitudeE6()/1E6);
sBuf.append(',');
sBuf.append(points.get(points.size()-1).getLongitudeE6()/1E6);
sBuf.append("&sensor=true&mode="+routeMode);
Logger.appendLog(sBuf.toString());
parser = new GoogleParser(sBuf.toString());
Route r = parser.parse();
You can parsing them using google http java client. Try this example
How to request Android download manager to download multiple files at the same time. Also I would like to know each and every file download status.
Request the first one.
Then, request the second one.
Then, request the third one.
Continue as needed.
Whether they download "at the same time" is not your concern, nor do you have control over it. They will download when DownloadManager decides to download them, which may be simultaneously or not.
1. Register listener for download complete
IntentFilter intentFilter = new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE);
registerReceiver(downloadReceiver, intentFilter);
2.Make request
Uri downloadUri = Uri.parse(entry.getValue());
DownloadManager.Request request = new DownloadManager.Request(
downloadUri);
request.setDestinationUri(path_to _file_store)));
downloadManager.enqueue(request);
3.Check status in listener
private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context arg0, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
long downloadId = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, 0);
System.out.println("download id=" + downloadId);
CheckDwnloadStatus(downloadId);
}
}
};
private void CheckDwnloadStatus(long id) {
// TODO Auto-generated method stub
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(id);
Cursor cursor = downloadManager.query(query);
if (cursor.moveToFirst()) {
int columnIndex = cursor
.getColumnIndex(DownloadManager.COLUMN_STATUS);
int status = cursor.getInt(columnIndex);
int columnReason = cursor
.getColumnIndex(DownloadManager.COLUMN_REASON);
int reason = cursor.getInt(columnReason);
switch (status) {
case DownloadManager.STATUS_FAILED:
String failedReason = "";
switch (reason) {
case DownloadManager.ERROR_CANNOT_RESUME:
failedReason = "ERROR_CANNOT_RESUME";
break;
case DownloadManager.ERROR_DEVICE_NOT_FOUND:
failedReason = "ERROR_DEVICE_NOT_FOUND";
break;
case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
failedReason = "ERROR_FILE_ALREADY_EXISTS";
break;
case DownloadManager.ERROR_FILE_ERROR:
failedReason = "ERROR_FILE_ERROR";
break;
case DownloadManager.ERROR_HTTP_DATA_ERROR:
failedReason = "ERROR_HTTP_DATA_ERROR";
break;
case DownloadManager.ERROR_INSUFFICIENT_SPACE:
failedReason = "ERROR_INSUFFICIENT_SPACE";
break;
case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
failedReason = "ERROR_TOO_MANY_REDIRECTS";
break;
case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
failedReason = "ERROR_UNHANDLED_HTTP_CODE";
break;
case DownloadManager.ERROR_UNKNOWN:
failedReason = "ERROR_UNKNOWN";
break;
}
Toast.makeText(this, "FAILED: " + failedReason,
Toast.LENGTH_LONG).show();
break;
case DownloadManager.STATUS_PAUSED:
String pausedReason = "";
switch (reason) {
case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
pausedReason = "PAUSED_QUEUED_FOR_WIFI";
break;
case DownloadManager.PAUSED_UNKNOWN:
pausedReason = "PAUSED_UNKNOWN";
break;
case DownloadManager.PAUSED_WAITING_FOR_NETWORK:
pausedReason = "PAUSED_WAITING_FOR_NETWORK";
break;
case DownloadManager.PAUSED_WAITING_TO_RETRY:
pausedReason = "PAUSED_WAITING_TO_RETRY";
break;
}
Toast.makeText(this, "PAUSED: " + pausedReason,
Toast.LENGTH_LONG).show();
break;
case DownloadManager.STATUS_PENDING:
Toast.makeText(this, "PENDING", Toast.LENGTH_LONG).show();
break;
case DownloadManager.STATUS_RUNNING:
Toast.makeText(this, "RUNNING", Toast.LENGTH_LONG).show();
break;
case DownloadManager.STATUS_SUCCESSFUL:
caluclateLoadingData();
// Toast.makeText(this, "SUCCESSFUL", Toast.LENGTH_LONG).show();
// GetFile();
break;
}
}
}
public final class Downloads {
/**
* #hide
*/
private Downloads() {}
/**
* The permission to access the download manager
* #hide .
*/
public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
/**
* The permission to access the download manager's advanced functions
* #hide
*/
public static final String PERMISSION_ACCESS_ADVANCED =
"android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
/**
* The permission to directly access the download manager's cache directory
* #hide
*/
public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
/**
* The permission to send broadcasts on download completion
* #hide
*/
public static final String PERMISSION_SEND_INTENTS =
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
* The content:// URI for the data table in the provider
* #hide
*/
public static final Uri CONTENT_URI =
Uri.parse("content://downloads/my_downloads");
/**
* Broadcast Action: this is sent by the download manager to the app
* that had initiated a download when that download completes. The
* download's content: uri is specified in the intent's data.
* #hide
*/
public static final String ACTION_DOWNLOAD_COMPLETED =
"android.intent.action.DOWNLOAD_COMPLETED";
/**
* Broadcast Action: this is sent by the download manager to the app
* that had initiated a download when the user selects the notification
* associated with that download. The download's content: uri is specified
* in the intent's data if the click is associated with a single download,
* or Downloads.CONTENT_URI if the notification is associated with
* multiple downloads.
* Note: this is not currently sent for downloads that have completed
* successfully.
* #hide
*/
public static final String ACTION_NOTIFICATION_CLICKED =
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
/**
* The name of the column containing the URI of the data being downloaded.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
* #hide
*/
public static final String COLUMN_URI = "uri";
public static final String COLUMN_APP_DATA = "entity";
public static final String COLUMN_NO_INTEGRITY = "no_integrity";
public static final String COLUMN_FILE_NAME_HINT = "hint";
public static final String _DATA = "_data";
public static final String COLUMN_MIME_TYPE = "mimetype";
public static final String COLUMN_DESTINATION = "destination";
public static final String COLUMN_VISIBILITY = "visibility";
public static final String COLUMN_CONTROL = "control";
public static final String COLUMN_STATUS = "status";
/**
* The name of the column containing the date at which some interesting
* status changed in the download. Stored as a System.currentTimeMillis()
* value.
* <P>Type: BIGINT</P>
* <P>Owner can Read</P>
* #hide
*/
public static final String COLUMN_LAST_MODIFICATION = "lastmod";
/**
* The name of the column containing the package name of the application
* that initiating the download. The download manager will send
* notifications to a component in this package when the download completes.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
* #hide
*/
public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
public static final String COLUMN_COOKIE_DATA = "cookiedata";
public static final String COLUMN_USER_AGENT = "useragent";
public static final String COLUMN_REFERER = "referer";
public static final String COLUMN_TOTAL_BYTES = "total_bytes";
public static final String COLUMN_CURRENT_BYTES = "current_bytes";
public static final String COLUMN_OTHER_UID = "otheruid";
public static final String COLUMN_TITLE = "title";
public static final String COLUMN_DESCRIPTION = "description";
public static final String COLUMN_DELETED = "deleted";
public static final int DESTINATION_EXTERNAL = 0;
public static final int DESTINATION_CACHE_PARTITION = 1;
public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
public static final int CONTROL_RUN = 0;
public static final int CONTROL_PAUSED = 1;
/**
* Returns whether the status is informational (i.e. 1xx).
* #hide
*/
public static boolean isStatusInformational(int status) {
return (status >= 100 && status < 200);
}
public static boolean isStatusSuccess(int status) {
return (status >= 200 && status < 300);
}
public static boolean isStatusError(int status) {
return (status >= 400 && status < 600);
}
public static boolean isStatusClientError(int status) {
return (status >= 400 && status < 500);
}
public static boolean isStatusServerError(int status) {
return (status >= 500 && status < 600);
}
public static boolean isStatusCompleted(int status) {
return (status >= 200 && status < 300) || (status >= 400 && status < 600);
}
public static final int STATUS_PENDING = 190;
public static final int STATUS_RUNNING = 192;
public static final int STATUS_SUCCESS = 200;
public static final int STATUS_BAD_REQUEST = 400;
public static final int STATUS_NOT_ACCEPTABLE = 406;
public static final int STATUS_LENGTH_REQUIRED = 411;
public static final int STATUS_PRECONDITION_FAILED = 412;
public static final int STATUS_CANCELED = 490;
public static final int STATUS_UNKNOWN_ERROR = 491;
public static final int STATUS_FILE_ERROR = 492;
public static final int STATUS_UNHANDLED_REDIRECT = 493;
public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
public static final int STATUS_HTTP_DATA_ERROR = 495;
public static final int STATUS_HTTP_EXCEPTION = 496;
public static final int STATUS_TOO_MANY_REDIRECTS = 497;
public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
public static final int VISIBILITY_VISIBLE = 0;
public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
public static final int VISIBILITY_HIDDEN = 2;
public static final class Impl implements BaseColumns {
private Impl() {}
/**
* The permission to access the download manager
*/
public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
/**
* The permission to access the download manager's advanced functions
*/
public static final String PERMISSION_ACCESS_ADVANCED =
"android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
/**
* The permission to access the all the downloads in the manager.
*/
public static final String PERMISSION_ACCESS_ALL =
"android.permission.ACCESS_ALL_DOWNLOADS";
/**
* The permission to directly access the download manager's cache
* directory
*/
public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
/**
* The permission to send broadcasts on download completion
*/
public static final String PERMISSION_SEND_INTENTS =
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
* The permission to download files to the cache partition that won't be automatically
* purged when space is needed.
*/
public static final String PERMISSION_CACHE_NON_PURGEABLE =
"android.permission.DOWNLOAD_CACHE_NON_PURGEABLE";
/**
* The permission to download files without any system notification being shown.
*/
public static final String PERMISSION_NO_NOTIFICATION =
"android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
/**
* The content:// URI to access downloads owned by the caller's UID.
*/
public static final Uri CONTENT_URI =
Uri.parse("content://downloads/my_downloads");
public static final Uri ALL_DOWNLOADS_CONTENT_URI =
Uri.parse("content://downloads/all_downloads");
public static final String ACTION_DOWNLOAD_COMPLETED =
"android.intent.action.DOWNLOAD_COMPLETED";
public static final String ACTION_NOTIFICATION_CLICKED =
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
public static final String COLUMN_URI = "uri";
public static final String COLUMN_APP_DATA = "entity";
public static final String COLUMN_NO_INTEGRITY = "no_integrity";
public static final String COLUMN_FILE_NAME_HINT = "hint";
public static final String _DATA = "_data";
public static final String COLUMN_MIME_TYPE = "mimetype";
public static final String COLUMN_DESTINATION = "destination";
public static final String COLUMN_VISIBILITY = "visibility";
public static final String COLUMN_CONTROL = "control";
public static final String COLUMN_STATUS = "status";
public static final String COLUMN_LAST_MODIFICATION = "lastmod";
public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
public static final String COLUMN_COOKIE_DATA = "cookiedata";
public static final String COLUMN_USER_AGENT = "useragent";
public static final String COLUMN_REFERER = "referer";
public static final String COLUMN_TOTAL_BYTES = "total_bytes";
public static final String COLUMN_CURRENT_BYTES = "current_bytes";
public static final String COLUMN_OTHER_UID = "otheruid";
public static final String COLUMN_TITLE = "title";
public static final String COLUMN_DESCRIPTION = "description";
public static final String COLUMN_IS_PUBLIC_API = "is_public_api";
public static final String COLUMN_ALLOW_ROAMING = "allow_roaming";
public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types";
public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui";
public static final String COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT =
"bypass_recommended_size_limit";
public static final String COLUMN_DELETED = "deleted";
public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
/*
* Lists the destinations that an application can specify for a download.
*/
public static final int DESTINATION_EXTERNAL = 0;
public static final int DESTINATION_CACHE_PARTITION = 1;
public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
/**
* This download will be saved to the download manager's private
* partition, as with DESTINATION_CACHE_PARTITION, but the download
* will not proceed if the user is on a roaming data connection.
*/
public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
/**
* This download will be saved to the location given by the file URI in
* {#link #COLUMN_FILE_NAME_HINT}.
*/
public static final int DESTINATION_FILE_URI = 4;
/**
* This download is allowed to run.
*/
public static final int CONTROL_RUN = 0;
/**
* This download must pause at the first opportunity.
*/
public static final int CONTROL_PAUSED = 1;
/**
* Returns whether the status is informational (i.e. 1xx).
*/
public static boolean isStatusInformational(int status) {
return (status >= 100 && status < 200);
}
/**
* Returns whether the status is a success (i.e. 2xx).
*/
public static boolean isStatusSuccess(int status) {
return (status >= 200 && status < 300);
}
/**
* Returns whether the status is an error (i.e. 4xx or 5xx).
*/
public static boolean isStatusError(int status) {
return (status >= 400 && status < 600);
}
/**
* Returns whether the status is a client error (i.e. 4xx).
*/
public static boolean isStatusClientError(int status) {
return (status >= 400 && status < 500);
}
/**
* Returns whether the status is a server error (i.e. 5xx).
*/
public static boolean isStatusServerError(int status) {
return (status >= 500 && status < 600);
}
/**
* Returns whether the download has completed (either with success or
* error).
*/
public static boolean isStatusCompleted(int status) {
return (status >= 200 && status < 300) || (status >= 400 && status < 600);
}
/**
* This download hasn't stated yet
*/
public static final int STATUS_PENDING = 190;
/**
* This download has started
*/
public static final int STATUS_RUNNING = 192;
/**
* This download has been paused by the owning app.
*/
public static final int STATUS_PAUSED_BY_APP = 193;
/**
* This download encountered some network error and is waiting before retrying the request.
*/
public static final int STATUS_WAITING_TO_RETRY = 194;
/**
* This download is waiting for network connectivity to proceed.
*/
public static final int STATUS_WAITING_FOR_NETWORK = 195;
/**
* This download exceeded a size limit for mobile networks and is waiting for a Wi-Fi
* connection to proceed.
*/
public static final int STATUS_QUEUED_FOR_WIFI = 196;
public static final int STATUS_SUCCESS = 200;
public static final int STATUS_BAD_REQUEST = 400;
/**
* This download can't be performed because the content type cannot be
* handled.
*/
public static final int STATUS_NOT_ACCEPTABLE = 406;
public static final int STATUS_LENGTH_REQUIRED = 411;
/**
* This download was interrupted and cannot be resumed.
* This is the code for the HTTP error "Precondition Failed", and it is
* also used in situations where the client doesn't have an ETag at all.
*/
public static final int STATUS_PRECONDITION_FAILED = 412;
/**
* The lowest-valued error status that is not an actual HTTP status code.
*/
public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;
/**
* The requested destination file already exists.
*/
public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;
/**
* Some possibly transient error occurred, but we can't resume the download.
*/
public static final int STATUS_CANNOT_RESUME = 489;
/**
* This download was canceled
*/
public static final int STATUS_CANCELED = 490;
public static final int STATUS_UNKNOWN_ERROR = 491;
public static final int STATUS_FILE_ERROR = 492;
/**
* This download couldn't be completed because of an HTTP
* redirect response that the download manager couldn't
* handle.
*/
public static final int STATUS_UNHANDLED_REDIRECT = 493;
/**
* This download couldn't be completed because of an
* unspecified unhandled HTTP code.
*/
public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
/**
* This download couldn't be completed because of an
* error receiving or processing data at the HTTP level.
*/
public static final int STATUS_HTTP_DATA_ERROR = 495;
/**
* This download couldn't be completed because of an
* HttpException while setting up the request.
*/
public static final int STATUS_HTTP_EXCEPTION = 496;
/**
* This download couldn't be completed because there were
* too many redirects.
*/
public static final int STATUS_TOO_MANY_REDIRECTS = 497;
/**
* This download couldn't be completed due to insufficient storage
* space. Typically, this is because the SD card is full.
*/
public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
/**
* This download couldn't be completed because no external storage
* device was found. Typically, this is because the SD card is not
* mounted.
*/
public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
/**
* This download is visible but only shows in the notifications
* while it's in progress.
*/
public static final int VISIBILITY_VISIBLE = 0;
/**
* This download is visible and shows in the notifications while
* in progress and after completion.
*/
public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
/**
* This download doesn't show in the UI or in the notifications.
*/
public static final int VISIBILITY_HIDDEN = 2;
/**
* Constants related to HTTP request headers associated with each download.
*/
public static class RequestHeaders {
public static final String HEADERS_DB_TABLE = "request_headers";
public static final String COLUMN_DOWNLOAD_ID = "download_id";
public static final String COLUMN_HEADER = "header";
public static final String COLUMN_VALUE = "value";
/**
* Path segment to add to a download URI to retrieve request headers
*/
public static final String URI_SEGMENT = "headers";
/**
* Prefix for ContentValues keys that contain HTTP header lines, to be passed to
* DownloadProvider.insert().
*/
public static final String INSERT_KEY_PREFIX = "http_header_";
}
}
}
public class DownloadManager {
private static final String TAG = "DownloadManager";
public final static String COLUMN_ID = BaseColumns._ID;
public final static String COLUMN_TITLE = "title";
public final static String COLUMN_DESCRIPTION = "description";
public final static String COLUMN_URI = "uri";
public final static String COLUMN_MEDIA_TYPE = "media_type";
public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
public final static String COLUMN_LOCAL_URI = "local_uri";
public final static String COLUMN_STATUS = "status";
public final static String COLUMN_REASON = "reason";
public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
public static final String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
public final static int STATUS_PENDING = 1 << 0;
public final static int STATUS_RUNNING = 1 << 1;
public final static int STATUS_PAUSED = 1 << 2;
public final static int STATUS_SUCCESSFUL = 1 << 3;
public final static int STATUS_FAILED = 1 << 4;
public final static int ERROR_UNKNOWN = 1000;
public final static int ERROR_FILE_ERROR = 1001;
public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
public final static int ERROR_HTTP_DATA_ERROR = 1004;
public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
public final static int ERROR_INSUFFICIENT_SPACE = 1006;
public final static int ERROR_DEVICE_NOT_FOUND = 1007;
public final static int ERROR_CANNOT_RESUME = 1008;
public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
public final static int PAUSED_WAITING_TO_RETRY = 1;
public final static int PAUSED_WAITING_FOR_NETWORK = 2;
public final static int PAUSED_QUEUED_FOR_WIFI = 3;
public final static int PAUSED_UNKNOWN = 4;
public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
public final static String ACTION_NOTIFICATION_CLICKED = "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
// this array must contain all public columns
private static final String[] COLUMNS = new String[] { COLUMN_ID,
COLUMN_MEDIAPROVIDER_URI, COLUMN_TITLE, COLUMN_DESCRIPTION,
COLUMN_URI, COLUMN_MEDIA_TYPE, COLUMN_TOTAL_SIZE_BYTES,
COLUMN_LOCAL_URI, COLUMN_STATUS, COLUMN_REASON,
COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP };
// columns to request from DownloadProvider
private static final String[] UNDERLYING_COLUMNS = new String[] {
Downloads.Impl._ID, Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
Downloads.COLUMN_TITLE, Downloads.COLUMN_DESCRIPTION,
Downloads.COLUMN_URI, Downloads.COLUMN_MIME_TYPE,
Downloads.COLUMN_TOTAL_BYTES, Downloads.COLUMN_STATUS,
Downloads.COLUMN_CURRENT_BYTES, Downloads.COLUMN_LAST_MODIFICATION,
Downloads.COLUMN_DESTINATION, Downloads.Impl.COLUMN_FILE_NAME_HINT,
Downloads.Impl._DATA, };
private static final Set<String> LONG_COLUMNS = new HashSet<String>(
Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS,
COLUMN_REASON, COLUMN_BYTES_DOWNLOADED_SO_FAR,
COLUMN_LAST_MODIFIED_TIMESTAMP));
public static class Request {
public static final int NETWORK_MOBILE = 1 << 0;
public static final int NETWORK_WIFI = 1 << 1;
private Uri mUri;
private Uri mDestinationUri;
private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
private CharSequence mTitle;
private CharSequence mDescription;
private boolean mShowNotification = true;
private String mMimeType;
private boolean mRoamingAllowed = true;
private int mAllowedNetworkTypes = ~0; // default to all network types
// allowed
private boolean mIsVisibleInDownloadsUi = true;
/**
* #param uri
* the HTTP URI to download.
*/
public Request(Uri uri) {
if (uri == null) {
throw new NullPointerException();
}
String scheme = uri.getScheme();
if (scheme == null
|| !(scheme.equals("http") || scheme.equals("https"))) {
throw new IllegalArgumentException(
"Can only download HTTP URIs: " + uri);
}
mUri = uri;
}
public Request setDestinationUri(Uri uri) {
mDestinationUri = uri;
return this;
}
public Request setDestinationInExternalFilesDir(Context context,
String dirType, String subPath) {
setDestinationFromBase(context.getExternalFilesDir(dirType),
subPath);
return this;
}
public Request setDestinationInExternalPublicDir(String dirType,
String subPath) {
setDestinationFromBase(
Environment.getExternalStoragePublicDirectory(dirType),
subPath);
return this;
}
private void setDestinationFromBase(File base, String subPath) {
if (subPath == null) {
throw new NullPointerException("subPath cannot be null");
}
mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
}
public Request addRequestHeader(String header, String value) {
if (header == null) {
throw new NullPointerException("header cannot be null");
}
if (header.contains(":")) {
throw new IllegalArgumentException("header may not contain ':'");
}
if (value == null) {
value = "";
}
mRequestHeaders.add(Pair.create(header, value));
return this;
}
public Request setTitle(CharSequence title) {
mTitle = title;
return this;
}
public Request setDescription(CharSequence description) {
mDescription = description;
return this;
}
public Request setMimeType(String mimeType) {
mMimeType = mimeType;
return this;
}
public Request setShowRunningNotification(boolean show) {
mShowNotification = show;
return this;
}
public Request setAllowedNetworkTypes(int flags) {
mAllowedNetworkTypes = flags;
return this;
}
public Request setAllowedOverRoaming(boolean allowed) {
mRoamingAllowed = allowed;
return this;
}
public Request setVisibleInDownloadsUi(boolean isVisible) {
mIsVisibleInDownloadsUi = isVisible;
return this;
}
/**
* #return ContentValues to be passed to DownloadProvider.insert()
*/
ContentValues toContentValues(String packageName) {
ContentValues values = new ContentValues();
assert mUri != null;
values.put(Downloads.COLUMN_URI, mUri.toString());
values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE, packageName);
if (mDestinationUri != null) {
values.put(Downloads.COLUMN_DESTINATION,
Downloads.Impl.DESTINATION_FILE_URI);
values.put(Downloads.COLUMN_FILE_NAME_HINT,
mDestinationUri.toString());
} else {
values.put(Downloads.COLUMN_DESTINATION,
Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE);
}
if (!mRequestHeaders.isEmpty()) {
encodeHttpHeaders(values);
}
putIfNonNull(values, Downloads.COLUMN_TITLE, mTitle);
putIfNonNull(values, Downloads.COLUMN_DESCRIPTION, mDescription);
putIfNonNull(values, Downloads.COLUMN_MIME_TYPE, mMimeType);
values.put(Downloads.COLUMN_VISIBILITY,
mShowNotification ? Downloads.VISIBILITY_VISIBLE
: Downloads.VISIBILITY_HIDDEN);
values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES,
mAllowedNetworkTypes);
values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI,
mIsVisibleInDownloadsUi);
return values;
}
private void encodeHttpHeaders(ContentValues values) {
int index = 0;
for (Pair<String, String> header : mRequestHeaders) {
String headerString = header.first + ": " + header.second;
values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX
+ index, headerString);
index++;
}
}
private void putIfNonNull(ContentValues contentValues, String key,
Object value) {
if (value != null) {
contentValues.put(key, value.toString());
}
}
}
/**
* This class may be used to filter download manager queries.
*/
public static class Query {
/**
* Constant for use with {#link #orderBy}
*
* #hide
*/
public static final int ORDER_ASCENDING = 1;
/**
* Constant for use with {#link #orderBy}
*
* #hide
*/
public static final int ORDER_DESCENDING = 2;
private long[] mIds = null;
private Integer mStatusFlags = null;
private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
private int mOrderDirection = ORDER_DESCENDING;
private boolean mOnlyIncludeVisibleInDownloadsUi = false;
/**
* Include only the downloads with the given IDs.
*
* #return this object
*/
public Query setFilterById(long... ids) {
mIds = ids;
return this;
}
/**
* Include only downloads with status matching any the given status
* flags.
*
* #param flags
* any combination of the STATUS_* bit flags
* #return this object
*/
public Query setFilterByStatus(int flags) {
mStatusFlags = flags;
return this;
}
/**
* Controls whether this query includes downloads not visible in the
* system's Downloads UI.
*
* #param value
* if true, this query will only include downloads that
* should be displayed in the system's Downloads UI; if false
* (the default), this query will include both visible and
* invisible downloads.
* #return this object
* #hide
*/
public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
mOnlyIncludeVisibleInDownloadsUi = value;
return this;
}
/**
* Change the sort order of the returned Cursor.
*
* #param column
* one of the COLUMN_* constants; currently, only
* {#link #COLUMN_LAST_MODIFIED_TIMESTAMP} and
* {#link #COLUMN_TOTAL_SIZE_BYTES} are supported.
* #param direction
* either {#link #ORDER_ASCENDING} or
* {#link #ORDER_DESCENDING}
* #return this object
* #hide
*/
public Query orderBy(String column, int direction) {
if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
throw new IllegalArgumentException("Invalid direction: "
+ direction);
}
if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
} else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
mOrderByColumn = Downloads.COLUMN_TOTAL_BYTES;
} else {
throw new IllegalArgumentException("Cannot order by " + column);
}
mOrderDirection = direction;
return this;
}
/**
* Run this query using the given ContentResolver.
*
* #param projection
* the projection to pass to ContentResolver.query()
* #return the Cursor returned by ContentResolver.query()
*/
Cursor runQuery(ContentResolver resolver, String[] projection,
Uri baseUri) {
Uri uri = baseUri;
List<String> selectionParts = new ArrayList<String>();
String[] selectionArgs = null;
if (mIds != null) {
selectionParts.add(getWhereClauseForIds(mIds));
selectionArgs = getWhereArgsForIds(mIds);
}
if (mStatusFlags != null) {
List<String> parts = new ArrayList<String>();
if ((mStatusFlags & STATUS_PENDING) != 0) {
parts.add(statusClause("=", Downloads.STATUS_PENDING));
}
if ((mStatusFlags & STATUS_RUNNING) != 0) {
parts.add(statusClause("=", Downloads.STATUS_RUNNING));
}
if ((mStatusFlags & STATUS_PAUSED) != 0) {
parts.add(statusClause("=",
Downloads.Impl.STATUS_PAUSED_BY_APP));
parts.add(statusClause("=",
Downloads.Impl.STATUS_WAITING_TO_RETRY));
parts.add(statusClause("=",
Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
parts.add(statusClause("=",
Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
}
if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
parts.add(statusClause("=", Downloads.STATUS_SUCCESS));
}
if ((mStatusFlags & STATUS_FAILED) != 0) {
parts.add("(" + statusClause(">=", 400) + " AND "
+ statusClause("<", 600) + ")");
}
selectionParts.add(joinStrings(" OR ", parts));
}
if (mOnlyIncludeVisibleInDownloadsUi) {
selectionParts
.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI
+ " != '0'");
}
// only return rows which are not marked 'deleted = 1'
selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
String selection = joinStrings(" AND ", selectionParts);
String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC"
: "DESC");
String orderBy = mOrderByColumn + " " + orderDirection;
return resolver.query(uri, projection, selection, selectionArgs,
orderBy);
}
private String joinStrings(String joiner, Iterable<String> parts) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (String part : parts) {
if (!first) {
builder.append(joiner);
}
builder.append(part);
first = false;
}
return builder.toString();
}
private String statusClause(String operator, int value) {
return Downloads.COLUMN_STATUS + operator + "'" + value + "'";
}
}
private ContentResolver mResolver;
private String mPackageName;
private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
/**
* #hide
*/
public DownloadManager(ContentResolver resolver, String packageName) {
mResolver = resolver;
mPackageName = packageName;
}
/**
* Makes this object access the download provider through /all_downloads
* URIs rather than /my_downloads URIs, for clients that have permission to
* do so.
*
* #hide
*/
public void setAccessAllDownloads(boolean accessAllDownloads) {
if (accessAllDownloads) {
mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
} else {
mBaseUri = Downloads.Impl.CONTENT_URI;
}
}
public long enqueue(Request request) {
ContentValues values = request.toContentValues(mPackageName);
Uri downloadUri = mResolver.insert(Downloads.CONTENT_URI, values);
long id = Long.parseLong(downloadUri.getLastPathSegment());
return id;
}
public int markRowDeleted(long... ids) {
if (ids == null || ids.length == 0) {
// called with nothing to remove!
throw new IllegalArgumentException(
"input param 'ids' can't be null");
}
ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_DELETED, 1);
return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
getWhereArgsForIds(ids));
}
public int remove(long... ids) {
if (ids == null || ids.length == 0) {
// called with nothing to remove!
throw new IllegalArgumentException(
"input param 'ids' can't be null");
}
return mResolver.delete(mBaseUri, getWhereClauseForIds(ids),
getWhereArgsForIds(ids));
}
public Cursor query(Query query) {
Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS,
mBaseUri);
if (underlyingCursor == null) {
return null;
}
return new CursorTranslator(underlyingCursor, mBaseUri);
}
public ParcelFileDescriptor openDownloadedFile(long id)
throws FileNotFoundException {
return mResolver.openFileDescriptor(getDownloadUri(id), "r");
}
public void restartDownload(long... ids) {
Cursor cursor = query(new Query().setFilterById(ids));
try {
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor
.moveToNext()) {
int status = cursor
.getInt(cursor.getColumnIndex(COLUMN_STATUS));
if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
throw new IllegalArgumentException(
"Cannot restart incomplete download: "
+ cursor.getLong(cursor
.getColumnIndex(COLUMN_ID)));
}
}
} finally {
cursor.close();
}
ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
values.putNull(Downloads.Impl._DATA);
values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
getWhereArgsForIds(ids));
}
/**
* Get the DownloadProvider URI for the download with the given ID.
*/
Uri getDownloadUri(long id) {
return ContentUris.withAppendedId(mBaseUri, id);
}
/**
* Get a parameterized SQL WHERE clause to select a bunch of IDs.
*/
static String getWhereClauseForIds(long[] ids) {
StringBuilder whereClause = new StringBuilder();
whereClause.append("(");
for (int i = 0; i < ids.length; i++) {
if (i > 0) {
whereClause.append("OR ");
}
whereClause.append(Downloads.Impl._ID);
whereClause.append(" = ? ");
}
whereClause.append(")");
return whereClause.toString();
}
/**
* Get the selection args for a clause returned by
* {#link #getWhereClauseForIds(long[])}.
*/
static String[] getWhereArgsForIds(long[] ids) {
String[] whereArgs = new String[ids.length];
for (int i = 0; i < ids.length; i++) {
whereArgs[i] = Long.toString(ids[i]);
}
return whereArgs;
}
/**
* This class wraps a cursor returned by DownloadProvider -- the
* "underlying cursor" -- and presents a different set of columns, those
* defined in the DownloadManager.COLUMN_* constants. Some columns
* correspond directly to underlying values while others are computed from
* underlying data.
*/
private static class CursorTranslator extends CursorWrapper {
private Uri mBaseUri;
public CursorTranslator(Cursor cursor, Uri baseUri) {
super(cursor);
mBaseUri = baseUri;
}
#Override
public int getColumnIndex(String columnName) {
return Arrays.asList(COLUMNS).indexOf(columnName);
}
#Override
public int getColumnIndexOrThrow(String columnName)
throws IllegalArgumentException {
int index = getColumnIndex(columnName);
if (index == -1) {
throw new IllegalArgumentException("No such column: "
+ columnName);
}
return index;
}
#Override
public String getColumnName(int columnIndex) {
int numColumns = COLUMNS.length;
if (columnIndex < 0 || columnIndex >= numColumns) {
throw new IllegalArgumentException("Invalid column index "
+ columnIndex + ", " + numColumns + " columns exist");
}
return COLUMNS[columnIndex];
}
#Override
public String[] getColumnNames() {
String[] returnColumns = new String[COLUMNS.length];
System.arraycopy(COLUMNS, 0, returnColumns, 0, COLUMNS.length);
return returnColumns;
}
#Override
public int getColumnCount() {
return COLUMNS.length;
}
#Override
public byte[] getBlob(int columnIndex) {
throw new UnsupportedOperationException();
}
#Override
public double getDouble(int columnIndex) {
return getLong(columnIndex);
}
private boolean isLongColumn(String column) {
return LONG_COLUMNS.contains(column);
}
#Override
public float getFloat(int columnIndex) {
return (float) getDouble(columnIndex);
}
#Override
public int getInt(int columnIndex) {
return (int) getLong(columnIndex);
}
#Override
public long getLong(int columnIndex) {
return translateLong(getColumnName(columnIndex));
}
#Override
public short getShort(int columnIndex) {
return (short) getLong(columnIndex);
}
#Override
public String getString(int columnIndex) {
return translateString(getColumnName(columnIndex));
}
private String translateString(String column) {
if (isLongColumn(column)) {
return Long.toString(translateLong(column));
}
if (column.equals(COLUMN_TITLE)) {
return getUnderlyingString(Downloads.COLUMN_TITLE);
}
if (column.equals(COLUMN_DESCRIPTION)) {
return getUnderlyingString(Downloads.COLUMN_DESCRIPTION);
}
if (column.equals(COLUMN_URI)) {
return getUnderlyingString(Downloads.COLUMN_URI);
}
if (column.equals(COLUMN_MEDIA_TYPE)) {
return getUnderlyingString(Downloads.COLUMN_MIME_TYPE);
}
if (column.equals(COLUMN_MEDIAPROVIDER_URI)) {
return getUnderlyingString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
}
assert column.equals(COLUMN_LOCAL_URI);
return getLocalUri();
}
private String getLocalUri() {
long destinationType = getUnderlyingLong(Downloads.Impl.COLUMN_DESTINATION);
if (destinationType == Downloads.Impl.DESTINATION_FILE_URI) {
// return client-provided file URI for external download
return getUnderlyingString(Downloads.Impl.COLUMN_FILE_NAME_HINT);
}
if (destinationType == Downloads.Impl.DESTINATION_EXTERNAL) {
// return stored destination for legacy external download
String localPath = getUnderlyingString(Downloads.Impl._DATA);
if (localPath == null) {
return null;
}
return Uri.fromFile(new File(localPath)).toString();
}
// return content URI for cache download
long downloadId = getUnderlyingLong(Downloads.Impl._ID);
return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
}
private long translateLong(String column) {
if (!isLongColumn(column)) {
// mimic behavior of underlying cursor -- most likely, throw
// NumberFormatException
return Long.valueOf(translateString(column));
}
if (column.equals(COLUMN_ID)) {
return getUnderlyingLong(Downloads.Impl._ID);
}
if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
return getUnderlyingLong(Downloads.COLUMN_TOTAL_BYTES);
}
if (column.equals(COLUMN_STATUS)) {
return translateStatus((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
}
if (column.equals(COLUMN_REASON)) {
return getReason((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
}
if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) {
return getUnderlyingLong(Downloads.COLUMN_CURRENT_BYTES);
}
assert column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP);
return getUnderlyingLong(Downloads.COLUMN_LAST_MODIFICATION);
}
private long getReason(int status) {
switch (translateStatus(status)) {
case STATUS_FAILED:
return getErrorCode(status);
case STATUS_PAUSED:
return getPausedReason(status);
default:
return 0; // arbitrary value when status is not an error
}
}
private long getPausedReason(int status) {
switch (status) {
case Downloads.Impl.STATUS_WAITING_TO_RETRY:
return PAUSED_WAITING_TO_RETRY;
case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
return PAUSED_WAITING_FOR_NETWORK;
case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
return PAUSED_QUEUED_FOR_WIFI;
default:
return PAUSED_UNKNOWN;
}
}
private long getErrorCode(int status) {
if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
|| (500 <= status && status < 600)) {
// HTTP status code
return status;
}
switch (status) {
case Downloads.STATUS_FILE_ERROR:
return ERROR_FILE_ERROR;
case Downloads.STATUS_UNHANDLED_HTTP_CODE:
case Downloads.STATUS_UNHANDLED_REDIRECT:
return ERROR_UNHANDLED_HTTP_CODE;
case Downloads.STATUS_HTTP_DATA_ERROR:
return ERROR_HTTP_DATA_ERROR;
case Downloads.STATUS_TOO_MANY_REDIRECTS:
return ERROR_TOO_MANY_REDIRECTS;
case Downloads.STATUS_INSUFFICIENT_SPACE_ERROR:
return ERROR_INSUFFICIENT_SPACE;
case Downloads.STATUS_DEVICE_NOT_FOUND_ERROR:
return ERROR_DEVICE_NOT_FOUND;
case Downloads.Impl.STATUS_CANNOT_RESUME:
return ERROR_CANNOT_RESUME;
case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
return ERROR_FILE_ALREADY_EXISTS;
default:
return ERROR_UNKNOWN;
}
}
private long getUnderlyingLong(String column) {
return super.getLong(super.getColumnIndex(column));
}
private String getUnderlyingString(String column) {
return super.getString(super.getColumnIndex(column));
}
private int translateStatus(int status) {
switch (status) {
case Downloads.STATUS_PENDING:
return STATUS_PENDING;
case Downloads.STATUS_RUNNING:
return STATUS_RUNNING;
case Downloads.Impl.STATUS_PAUSED_BY_APP:
case Downloads.Impl.STATUS_WAITING_TO_RETRY:
case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
return STATUS_PAUSED;
case Downloads.STATUS_SUCCESS:
return STATUS_SUCCESSFUL;
default:
assert Downloads.isStatusError(status);
return STATUS_FAILED;
}
}
}
}