Multiple tags support added to Proximity profile

This commit is contained in:
Aleksander Nowakowski
2016-10-17 18:10:54 +02:00
parent 78728296dd
commit fe3ffce9f0
31 changed files with 1125 additions and 610 deletions

View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) 2016, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.nrftoolbox.proximity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import no.nordicsemi.android.nrftoolbox.R;
public class DeviceAdapter extends RecyclerView.Adapter<DeviceAdapter.ViewHolder> {
private final ProximityService.ProximityBinder mService;
private final List<BluetoothDevice> mDevices;
public DeviceAdapter(final ProximityService.ProximityBinder binder) {
mService = binder;
mDevices = mService.getManagedDevices();
}
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_feature_proximity_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.bind(mDevices.get(position));
}
@Override
public int getItemCount() {
return mDevices.size();
}
public void onDeviceAdded(final BluetoothDevice device) {
notifyItemInserted(mDevices.size() - 1);
}
public void onDeviceRemoved(final BluetoothDevice device) {
notifyDataSetChanged(); // we don't have position of the removed device here
}
public void onDeviceStateChanged(final BluetoothDevice device) {
final int position = mDevices.indexOf(device);
if (position >= 0)
notifyItemChanged(position);
}
public void onBatteryValueReceived(final BluetoothDevice device) {
final int position = mDevices.indexOf(device);
if (position >= 0)
notifyItemChanged(position);
}
public class ViewHolder extends RecyclerView.ViewHolder {
private ImageView iconView;
private TextView nameView;
private TextView addressView;
private TextView batteryView;
private ImageButton actionButton;
public ViewHolder(final View itemView) {
super(itemView);
iconView = (ImageView) itemView.findViewById(R.id.icon);
nameView = (TextView) itemView.findViewById(R.id.name);
addressView = (TextView) itemView.findViewById(R.id.address);
batteryView = (TextView) itemView.findViewById(R.id.battery);
actionButton = (ImageButton) itemView.findViewById(R.id.action_find_silent);
// Configure FIND / SILENT button
actionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
final int position = getAdapterPosition();
final BluetoothDevice device = mDevices.get(position);
final boolean on = mService.toggleImmediateAlert(device);
actionButton.setImageResource(on ? R.drawable.ic_stat_notify_proximity_silent : R.drawable.ic_stat_notify_proximity_find);
}
});
// Configure Disconnect button
itemView.findViewById(R.id.action_disconnect).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
final int position = getAdapterPosition();
final BluetoothDevice device = mDevices.get(position);
mService.disconnect(device);
}
});
}
private void bind(final BluetoothDevice device) {
final int state = mService.getConnectionState(device);
switch (state) {
case BluetoothGatt.STATE_DISCONNECTED:
case BluetoothGatt.STATE_DISCONNECTING:
DrawableCompat.setTint(iconView.getDrawable(), ContextCompat.getColor(iconView.getContext(), android.R.color.black));
break;
case BluetoothGatt.STATE_CONNECTING:
DrawableCompat.setTint(iconView.getDrawable(), ContextCompat.getColor(iconView.getContext(), R.color.actionBarColor));
break;
case BluetoothGatt.STATE_CONNECTED:
DrawableCompat.setTint(iconView.getDrawable(), ContextCompat.getColor(iconView.getContext(), R.color.actionBarColorDark));
break;
}
String name = device.getName();
if (TextUtils.isEmpty(name))
name = nameView.getResources().getString(R.string.proximity_default_device_name);
nameView.setText(name);
addressView.setText(device.getAddress());
final boolean on = mService.isImmediateAlertOn(device);
actionButton.setImageResource(on ? R.drawable.ic_stat_notify_proximity_silent : R.drawable.ic_stat_notify_proximity_find);
actionButton.setVisibility(state == BluetoothGatt.STATE_CONNECTED ? View.VISIBLE : View.GONE);
final int batteryValue = mService.getBatteryValue(device);
if (batteryValue >= 0) {
batteryView.getCompoundDrawables()[0 /*left*/].setLevel(batteryValue);
batteryView.setVisibility(View.VISIBLE);
batteryView.setText(batteryView.getResources().getString(R.string.battery, batteryValue));
batteryView.setAlpha(state == BluetoothGatt.STATE_CONNECTED ? 1.0f : 0.5f);
}
}
}
}

View File

@@ -22,30 +22,26 @@
package no.nordicsemi.android.nrftoolbox.proximity;
import android.bluetooth.BluetoothDevice;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
import no.nordicsemi.android.nrftoolbox.profile.multiconnect.BleMulticonnectProfileService;
import no.nordicsemi.android.nrftoolbox.profile.multiconnect.BleMulticonnectProfileServiceReadyActivity;
import no.nordicsemi.android.nrftoolbox.widget.DividerItemDecoration;
public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityService.ProximityBinder> {
public class ProximityActivity extends BleMulticonnectProfileServiceReadyActivity<ProximityService.ProximityBinder> {
private static final String TAG = "ProximityActivity";
public static final String PREFS_GATT_SERVER_ENABLED = "prefs_gatt_server_enabled";
// This is not used any more. Server is created always after the service is started or
// after Bluetooth adapter is enabled.
// public static final String PREFS_GATT_SERVER_ENABLED = "prefs_gatt_server_enabled";
private Button mFindMeButton;
private ImageView mLockImage;
private CheckBox mGattServerSwitch;
private RecyclerView mDevicesView;
private DeviceAdapter mAdapter;
@Override
protected void onCreateView(final Bundle savedInstanceState) {
@@ -54,18 +50,9 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
}
private void setGUI() {
mFindMeButton = (Button) findViewById(R.id.action_findme);
mLockImage = (ImageView) findViewById(R.id.imageLock);
mGattServerSwitch = (CheckBox) findViewById(R.id.option);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ProximityActivity.this);
mGattServerSwitch.setChecked(preferences.getBoolean(PREFS_GATT_SERVER_ENABLED, true));
mGattServerSwitch.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
preferences.edit().putBoolean(PREFS_GATT_SERVER_ENABLED, isChecked).apply();
}
});
final RecyclerView recyclerView = mDevicesView = (RecyclerView) findViewById(android.R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
}
@Override
@@ -75,24 +62,16 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
@Override
protected void onServiceBinded(final ProximityService.ProximityBinder binder) {
mGattServerSwitch.setEnabled(false);
if (binder.isConnected()) {
showOpenLock();
if (binder.isImmediateAlertOn()) {
showSilentMeOnButton();
}
}
mDevicesView.setAdapter(mAdapter = new DeviceAdapter(binder));
}
@Override
protected void onServiceUnbinded() {
// you may release the binder instance here
mDevicesView.setAdapter(mAdapter = null);
}
@Override
protected Class<? extends BleProfileService> getServiceClass() {
protected Class<? extends BleMulticonnectProfileService> getServiceClass() {
return ProximityService.class;
}
@@ -101,102 +80,59 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
return R.string.proximity_about_text;
}
@Override
protected int getDefaultDeviceName() {
return R.string.proximity_default_name;
}
@Override
protected UUID getFilterUUID() {
return ProximityManager.LINKLOSS_SERVICE_UUID;
}
/**
* Callback of FindMe button on ProximityActivity
*/
public void onFindMeClicked(final View view) {
if (isBLEEnabled()) {
if (!isDeviceConnected()) {
// do nothing
} else if (getService().toggleImmediateAlert()) {
showSilentMeOnButton();
} else {
showFindMeOnButton();
}
} else {
showBLEDialog();
}
@Override
public void onDeviceConnecting(final BluetoothDevice device) {
if (mAdapter != null)
mAdapter.onDeviceAdded(device);
}
@Override
protected void setDefaultUI() {
mFindMeButton.setText(R.string.proximity_action_findme);
mLockImage.setImageResource(R.drawable.proximity_lock_closed);
}
@Override
public void onServicesDiscovered(final BluetoothDevice device, boolean optionalServicesFound) {
// this may notify user or update views
public void onDeviceConnected(final BluetoothDevice device) {
if (mAdapter != null)
mAdapter.onDeviceStateChanged(device);
}
@Override
public void onDeviceReady(final BluetoothDevice device) {
showOpenLock();
if (mAdapter != null)
mAdapter.onDeviceStateChanged(device);
}
@Override
public void onDeviceDisconnecting(final BluetoothDevice device) {
if (mAdapter != null)
mAdapter.onDeviceStateChanged(device);
}
@Override
public void onDeviceDisconnected(final BluetoothDevice device) {
super.onDeviceDisconnected(device);
showClosedLock();
mGattServerSwitch.setEnabled(true);
if (mAdapter != null)
mAdapter.onDeviceRemoved(device);
}
@Override
public void onBondingRequired(final BluetoothDevice device) {
showClosedLock();
}
@Override
public void onBonded(final BluetoothDevice device) {
showOpenLock();
public void onDeviceNotSupported(final BluetoothDevice device) {
super.onDeviceNotSupported(device);
if (mAdapter != null)
mAdapter.onDeviceRemoved(device);
}
@Override
public void onLinklossOccur(final BluetoothDevice device) {
super.onLinklossOccur(device);
showClosedLock();
resetForLinkloss();
DebugLogger.w(TAG, "Linkloss occur");
String deviceName = getDeviceName();
if (deviceName == null) {
deviceName = getString(R.string.proximity_default_name);
}
showLinklossDialog(deviceName);
if (mAdapter != null)
mAdapter.onDeviceStateChanged(device);
showLinklossDialog(device.getName());
}
private void resetForLinkloss() {
setDefaultUI();
}
private void showFindMeOnButton() {
mFindMeButton.setText(R.string.proximity_action_findme);
}
private void showSilentMeOnButton() {
mFindMeButton.setText(R.string.proximity_action_silentme);
}
private void showOpenLock() {
mFindMeButton.setEnabled(true);
mLockImage.setImageResource(R.drawable.proximity_lock_open);
}
private void showClosedLock() {
mFindMeButton.setEnabled(false);
mLockImage.setImageResource(R.drawable.proximity_lock_closed);
@Override
public void onBatteryValueReceived(final BluetoothDevice device, final int value) {
if (mAdapter != null)
mAdapter.onBatteryValueReceived(device); // Value will be obtained from the service
}
private void showLinklossDialog(final String name) {

View File

@@ -21,30 +21,19 @@
*/
package no.nordicsemi.android.nrftoolbox.proximity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import java.util.Deque;
import java.util.LinkedList;
import java.util.UUID;
import no.nordicsemi.android.error.GattError;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.parser.AlertLevelParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
import no.nordicsemi.android.nrftoolbox.utility.ParserUtils;
public class ProximityManager extends BleManager<ProximityManagerCallbacks> {
private final String TAG = "ProximityManager";
@@ -57,16 +46,14 @@ public class ProximityManager extends BleManager<ProximityManagerCallbacks> {
private static final UUID ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-00805f9b34fb");
private final static byte[] HIGH_ALERT = { 0x02 };
private final static byte[] MILD_ALERT = { 0x01 };
private final static byte[] NO_ALERT = { 0x00 };
private BluetoothGattCharacteristic mAlertLevelCharacteristic, mLinklossCharacteristic;
private BluetoothGattServer mBluetoothGattServer;
private BluetoothDevice mDeviceToConnect;
private Handler mHandler;
private boolean mAlertOn;
public ProximityManager(Context context) {
public ProximityManager(final Context context) {
super(context);
mHandler = new Handler();
}
@Override
@@ -74,198 +61,6 @@ public class ProximityManager extends BleManager<ProximityManagerCallbacks> {
return true;
}
private void openGattServer(Context context, BluetoothManager manager) {
mBluetoothGattServer = manager.openGattServer(context, mGattServerCallbacks);
}
private void closeGattServer() {
if (mBluetoothGattServer != null) {
// mBluetoothGattServer.cancelConnection(mBluetoothGatt.getDevice()); // FIXME this method does not cancel the connection
mBluetoothGattServer.close(); // FIXME This method does not cause BluetoothGattServerCallback#onConnectionStateChange(newState=DISCONNECTED) to be called on Nexus phones.
mBluetoothGattServer = null;
}
}
private void addImmediateAlertService() {
/*
* This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (f.e. from onServiceAdded in gatt server callback) it hangs the app.
*/
final BluetoothGattCharacteristic alertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
BluetoothGattCharacteristic.PERMISSION_WRITE);
alertLevel.setValue(HIGH_ALERT);
final BluetoothGattService immediateAlertService = new BluetoothGattService(IMMEDIATE_ALERT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
immediateAlertService.addCharacteristic(alertLevel);
mBluetoothGattServer.addService(immediateAlertService);
}
private void addLinklossService() {
/*
* This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (f.e. from onServiceAdded in gatt server callback) it hangs the app.
*/
final BluetoothGattCharacteristic linklossAlertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE
| BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
linklossAlertLevel.setValue(HIGH_ALERT);
final BluetoothGattService linklossService = new BluetoothGattService(LINKLOSS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
linklossService.addCharacteristic(linklossAlertLevel);
mBluetoothGattServer.addService(linklossService);
}
private final BluetoothGattServerCallback mGattServerCallbacks = new BluetoothGattServerCallback() {
@Override
public void onServiceAdded(final int status, final BluetoothGattService service) {
Logger.v(mLogSession, "[Server] Service " + service.getUuid() + " added");
mHandler.post(new Runnable() {
@Override
public void run() {
// Adding another service from callback thread fails on Samsung S4 with Android 4.3
if (IMMEDIATE_ALERT_SERVICE_UUID.equals(service.getUuid()))
addLinklossService();
else {
Logger.i(mLogSession, "[Server] Gatt server started");
ProximityManager.super.connect(mDeviceToConnect);
mDeviceToConnect = null;
}
}
});
}
@Override
public void onConnectionStateChange(final BluetoothDevice device, final int status, final int newState) {
Logger.d(mLogSession, "[Server callback] Connection state changed with status: " + status + " and new state: " + stateToString(newState) + " (" + newState + ")");
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
Logger.i(mLogSession, "[Server] Device with address " + device.getAddress() + " connected");
} else {
Logger.i(mLogSession, "[Server] Device disconnected");
}
} else {
Logger.e(mLogSession, "[Server] Error " + status + " (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status));
}
}
@Override
public void onCharacteristicReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattCharacteristic characteristic) {
Logger.d(mLogSession, "[Server callback] Read request for characteristic " + characteristic.getUuid() + " (requestId=" + requestId + ", offset=" + offset + ")");
Logger.i(mLogSession, "[Server] READ request for characteristic " + characteristic.getUuid() + " received");
byte[] value = characteristic.getValue();
if (value != null && offset > 0) {
byte[] offsetValue = new byte[value.length - offset];
System.arraycopy(value, offset, offsetValue, 0, offsetValue.length);
value = offsetValue;
}
if (value != null)
Logger.d(mLogSession, "server.sendResponse(GATT_SUCCESS, value=" + ParserUtils.parse(value) + ")");
else
Logger.d(mLogSession, "server.sendResponse(GATT_SUCCESS, value=null)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
Logger.v(mLogSession, "[Server] Response sent");
}
@Override
public void onCharacteristicWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattCharacteristic characteristic, final boolean preparedWrite,
final boolean responseNeeded, final int offset, final byte[] value) {
Logger.d(mLogSession, "[Server callback] Write request to characteristic " + characteristic.getUuid()
+ " (requestId=" + requestId + ", prepareWrite=" + preparedWrite + ", responseNeeded=" + responseNeeded + ", offset=" + offset + ", value=" + ParserUtils.parse(value) + ")");
final String writeType = !responseNeeded ? "WRITE NO RESPONSE" : "WRITE COMMAND";
Logger.i(mLogSession, "[Server] " + writeType + " request for characteristic " + characteristic.getUuid() + " received, value: " + ParserUtils.parse(value));
if (offset == 0) {
characteristic.setValue(value);
} else {
final byte[] currentValue = characteristic.getValue();
final byte[] newValue = new byte[currentValue.length + value.length];
System.arraycopy(currentValue, 0, newValue, 0, currentValue.length);
System.arraycopy(value, 0, newValue, offset, value.length);
characteristic.setValue(newValue);
}
if (!preparedWrite && value != null && value.length == 1) { // small validation
if (value[0] != NO_ALERT[0]) {
Logger.a(mLogSession, "[Server] Immediate alarm request received: " + AlertLevelParser.parse(characteristic));
mCallbacks.onAlarmTriggered(device);
} else {
Logger.a(mLogSession, "[Server] Immediate alarm request received: OFF");
mCallbacks.onAlarmStopped(device);
}
}
Logger.d(mLogSession, "server.sendResponse(GATT_SUCCESS, offset=" + offset + ", value=" + ParserUtils.parse(value) + ")");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
Logger.v(mLogSession, "[Server] Response sent");
}
@Override
public void onDescriptorReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattDescriptor descriptor) {
Logger.d(mLogSession, "[Server callback] Write request to descriptor " + descriptor.getUuid() + " (requestId=" + requestId + ", offset=" + offset + ")");
Logger.i(mLogSession, "[Server] READ request for descriptor " + descriptor.getUuid() + " received");
// This method is not supported
Logger.w(mLogSession, "[Server] Operation not supported");
Logger.d(mLogSession, "[Server] server.sendResponse(GATT_REQUEST_NOT_SUPPORTED)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
Logger.v(mLogSession, "[Server] Response sent");
}
@Override
public void onDescriptorWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattDescriptor descriptor, final boolean preparedWrite,
final boolean responseNeeded, final int offset, final byte[] value) {
Logger.d(mLogSession, "[Server callback] Write request to descriptor " + descriptor.getUuid()
+ " (requestId=" + requestId + ", prepareWrite=" + preparedWrite + ", responseNeeded=" + responseNeeded + ", offset=" + offset + ", value=" + ParserUtils.parse(value) + ")");
Logger.i(mLogSession, "[Server] READ request for descriptor " + descriptor.getUuid() + " received");
// This method is not supported
Logger.w(mLogSession, "[Server] Operation not supported");
Logger.d(mLogSession, "[Server] server.sendResponse(GATT_REQUEST_NOT_SUPPORTED)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
Logger.v(mLogSession, "[Server] Response sent");
}
@Override
public void onExecuteWrite(final BluetoothDevice device, final int requestId, final boolean execute) {
Logger.d(mLogSession, "[Server callback] Execute write request (requestId=" + requestId + ", execute=" + execute + ")");
// This method is not supported
Logger.w(mLogSession, "[Server] Operation not supported");
Logger.d(mLogSession, "[Server] server.sendResponse(GATT_REQUEST_NOT_SUPPORTED)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, 0, null);
Logger.v(mLogSession, "[Server] Response sent");
}
};
@Override
public void connect(final BluetoothDevice device) {
// Should we use the GATT Server?
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
final boolean useGattServer = preferences.getBoolean(ProximityActivity.PREFS_GATT_SERVER_ENABLED, true);
if (useGattServer) {
// Save the device that we want to connect to. First we will create a GATT Server
mDeviceToConnect = device;
final BluetoothManager bluetoothManager = (BluetoothManager) getContext().getSystemService(Context.BLUETOOTH_SERVICE);
try {
DebugLogger.d(TAG, "[Server] Starting Gatt server...");
Logger.v(mLogSession, "[Server] Starting Gatt server...");
openGattServer(getContext(), bluetoothManager);
addImmediateAlertService();
// the BluetoothGattServerCallback#onServiceAdded callback will proceed further operations
} catch (final Exception e) {
// On Nexus 4&7 with Android 4.4 (build KRT16S) sometimes creating Gatt Server fails. There is a Null Pointer Exception thrown from addCharacteristic method.
Logger.e(mLogSession, "[Server] Gatt server failed to start");
Log.e(TAG, "Creating Gatt Server failed", e);
}
} else {
super.connect(device);
}
}
@Override
public boolean disconnect() {
final boolean result = super.disconnect();
closeGattServer();
return result;
}
@Override
protected BleManagerGattCallback getGattCallback() {
return mGattCallback;
@@ -313,31 +108,36 @@ public class ProximityManager extends BleManager<ProximityManagerCallbacks> {
}
};
public void writeImmediateAlertOn() {
/**
* Toggles the immediate alert on the target device.
* @return true if alarm has been enabled, false if disabled
*/
public boolean toggleImmediateAlert() {
writeImmediateAlert(!mAlertOn);
return mAlertOn;
}
/**
* Writes the HIGH ALERT or NO ALERT command to the target device
* @param on true to enable the alarm on proximity tag, false to disable it
*/
public void writeImmediateAlert(final boolean on) {
if (!isConnected())
return;
if (mAlertLevelCharacteristic != null) {
mAlertLevelCharacteristic.setValue(HIGH_ALERT);
mAlertLevelCharacteristic.setValue(on ? HIGH_ALERT : NO_ALERT);
writeCharacteristic(mAlertLevelCharacteristic);
mAlertOn = on;
} else {
DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found");
}
}
public void writeImmediateAlertOff() {
if (mAlertLevelCharacteristic != null) {
mAlertLevelCharacteristic.setValue(NO_ALERT);
writeCharacteristic(mAlertLevelCharacteristic);
} else {
DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found");
}
}
@Override
public void close() {
super.close();
if (mBluetoothGattServer != null) {
mBluetoothGattServer.close();
mBluetoothGattServer = null;
}
/**
* Returns true if the alert has been enabled on the proximity tag, false otherwise.
*/
public boolean isAlertEnabled() {
return mAlertOn;
}
}

View File

@@ -21,12 +21,8 @@
*/
package no.nordicsemi.android.nrftoolbox.proximity;
import android.bluetooth.BluetoothDevice;
import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
public interface ProximityManagerCallbacks extends BleManagerCallbacks {
void onAlarmTriggered(final BluetoothDevice device);
void onAlarmStopped(final BluetoothDevice device);
// No additional methods
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.nrftoolbox.proximity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import java.util.UUID;
import no.nordicsemi.android.error.GattError;
import no.nordicsemi.android.log.LogContract;
import no.nordicsemi.android.nrftoolbox.parser.AlertLevelParser;
import no.nordicsemi.android.nrftoolbox.profile.multiconnect.IDeviceLogger;
import no.nordicsemi.android.nrftoolbox.utility.ParserUtils;
public class ProximityServerManager {
private final String TAG = "ProximityServerManager";
/** Immediate Alert service UUID */
public final static UUID IMMEDIATE_ALERT_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb");
/** Linkloss service UUID */
public final static UUID LINKLOSS_SERVICE_UUID = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb");
/** Alert Level characteristic UUID */
private static final UUID ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-00805f9b34fb");
private final static byte[] HIGH_ALERT = { 0x02 };
private final static byte[] MILD_ALERT = { 0x01 };
private final static byte[] NO_ALERT = { 0x00 };
private BluetoothGattServer mBluetoothGattServer;
private ProximityServerManagerCallbacks mCallbacks;
private IDeviceLogger mLogger;
private Handler mHandler;
private OnServerOpenCallback mOnServerOpenCallback;
public interface OnServerOpenCallback {
/** Method called when the GATT server was created and all services were added successfully. */
void onGattServerOpen();
}
public ProximityServerManager(final ProximityServerManagerCallbacks callbacks) {
mHandler = new Handler();
mCallbacks = callbacks;
}
public void setLogger(final IDeviceLogger logger) {
mLogger = logger;
}
public void openGattServer(final Context context, final OnServerOpenCallback callback) {
mOnServerOpenCallback = callback;
final BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothGattServer = manager.openGattServer(context, mGattServerCallbacks);
addImmediateAlertService();
}
public void closeGattServer() {
if (mBluetoothGattServer != null) {
mBluetoothGattServer.close();
mBluetoothGattServer = null;
mOnServerOpenCallback = null;
}
}
public void cancelConnection(final BluetoothDevice device) {
if (mBluetoothGattServer != null) {
mLogger.log(device, LogContract.Log.Level.VERBOSE, "[Server] Cancelling server connection...");
mLogger.log(device, LogContract.Log.Level.DEBUG, "server.cancelConnection(device)");
mBluetoothGattServer.cancelConnection(device);
}
}
private void addImmediateAlertService() {
/*
* This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (e.g. from onServiceAdded in gatt server callback) it hangs the app.
*/
final BluetoothGattCharacteristic alertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
BluetoothGattCharacteristic.PERMISSION_WRITE);
alertLevel.setValue(NO_ALERT);
final BluetoothGattService immediateAlertService = new BluetoothGattService(IMMEDIATE_ALERT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
immediateAlertService.addCharacteristic(alertLevel);
mBluetoothGattServer.addService(immediateAlertService);
}
private void addLinklossService() {
/*
* This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (e.g. from onServiceAdded in gatt server callback) it hangs the app.
*/
final BluetoothGattCharacteristic linklossAlertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE
| BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
linklossAlertLevel.setValue(HIGH_ALERT);
final BluetoothGattService linklossService = new BluetoothGattService(LINKLOSS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
linklossService.addCharacteristic(linklossAlertLevel);
mBluetoothGattServer.addService(linklossService);
}
private final BluetoothGattServerCallback mGattServerCallbacks = new BluetoothGattServerCallback() {
@Override
public void onServiceAdded(final int status, final BluetoothGattService service) {
// Adding another service from callback thread fails on Samsung S4 with Android 4.3
mHandler.post(new Runnable() {
@Override
public void run() {
if (IMMEDIATE_ALERT_SERVICE_UUID.equals(service.getUuid())) {
addLinklossService();
} else if (mOnServerOpenCallback != null) {
mOnServerOpenCallback.onGattServerOpen();
mOnServerOpenCallback = null;
}
}
});
}
@Override
public void onConnectionStateChange(final BluetoothDevice device, final int status, final int newState) {
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server callback] Connection state changed with status: " + status + " and new state: " + stateToString(newState) + " (" + newState + ")");
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
mLogger.log(device, LogContract.Log.Level.INFO, "[Server] Device with address " + device.getAddress() + " connected");
} else {
mLogger.log(device, LogContract.Log.Level.INFO, "[Server] Device disconnected");
mCallbacks.onAlarmStopped(device);
}
} else {
mLogger.log(device, LogContract.Log.Level.ERROR, "[Server] Error " + status + " (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status));
}
}
@Override
public void onCharacteristicReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattCharacteristic characteristic) {
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server callback] Read request for characteristic " + characteristic.getUuid() + " (requestId=" + requestId + ", offset=" + offset + ")");
mLogger.log(device, LogContract.Log.Level.INFO, "[Server] READ request for characteristic " + characteristic.getUuid() + " received");
byte[] value = characteristic.getValue();
if (value != null && offset > 0) {
byte[] offsetValue = new byte[value.length - offset];
System.arraycopy(value, offset, offsetValue, 0, offsetValue.length);
value = offsetValue;
}
if (value != null)
mLogger.log(device, LogContract.Log.Level.DEBUG, "server.sendResponse(GATT_SUCCESS, value=" + ParserUtils.parseDebug(value) + ")");
else
mLogger.log(device, LogContract.Log.Level.DEBUG, "server.sendResponse(GATT_SUCCESS, value=null)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
mLogger.log(device, LogContract.Log.Level.VERBOSE, "[Server] Response sent");
}
@Override
public void onCharacteristicWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattCharacteristic characteristic, final boolean preparedWrite,
final boolean responseNeeded, final int offset, final byte[] value) {
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server callback] Write request to characteristic " + characteristic.getUuid()
+ " (requestId=" + requestId + ", prepareWrite=" + preparedWrite + ", responseNeeded=" + responseNeeded + ", offset=" + offset + ", value=" + ParserUtils.parseDebug(value) + ")");
final String writeType = !responseNeeded ? "WRITE NO RESPONSE" : "WRITE COMMAND";
mLogger.log(device, LogContract.Log.Level.INFO, "[Server] " + writeType + " request for characteristic " + characteristic.getUuid() + " received, value: " + ParserUtils.parse(value));
if (offset == 0) {
characteristic.setValue(value);
} else {
final byte[] currentValue = characteristic.getValue();
final byte[] newValue = new byte[currentValue.length + value.length];
System.arraycopy(currentValue, 0, newValue, 0, currentValue.length);
System.arraycopy(value, 0, newValue, offset, value.length);
characteristic.setValue(newValue);
}
if (!preparedWrite && value != null && value.length == 1) { // small validation
if (value[0] != NO_ALERT[0]) {
mLogger.log(device, LogContract.Log.Level.APPLICATION, "[Server] Immediate alarm request received: " + AlertLevelParser.parse(characteristic));
mCallbacks.onAlarmTriggered(device);
} else {
mLogger.log(device, LogContract.Log.Level.APPLICATION, "[Server] Immediate alarm request received: OFF");
mCallbacks.onAlarmStopped(device);
}
}
mLogger.log(device, LogContract.Log.Level.DEBUG, "server.sendResponse(GATT_SUCCESS, offset=" + offset + ", value=" + ParserUtils.parseDebug(value) + ")");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
mLogger.log(device, LogContract.Log.Level.VERBOSE, "[Server] Response sent");
}
@Override
public void onDescriptorReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattDescriptor descriptor) {
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server callback] Write request to descriptor " + descriptor.getUuid() + " (requestId=" + requestId + ", offset=" + offset + ")");
mLogger.log(device, LogContract.Log.Level.INFO, "[Server] READ request for descriptor " + descriptor.getUuid() + " received");
// This method is not supported
mLogger.log(device, LogContract.Log.Level.WARNING, "[Server] Operation not supported");
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server] server.sendResponse(GATT_REQUEST_NOT_SUPPORTED)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
mLogger.log(device, LogContract.Log.Level.VERBOSE, "[Server] Response sent");
}
@Override
public void onDescriptorWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattDescriptor descriptor, final boolean preparedWrite,
final boolean responseNeeded, final int offset, final byte[] value) {
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server callback] Write request to descriptor " + descriptor.getUuid()
+ " (requestId=" + requestId + ", prepareWrite=" + preparedWrite + ", responseNeeded=" + responseNeeded + ", offset=" + offset + ", value=" + ParserUtils.parse(value) + ")");
mLogger.log(device, LogContract.Log.Level.INFO, "[Server] READ request for descriptor " + descriptor.getUuid() + " received");
// This method is not supported
mLogger.log(device, LogContract.Log.Level.WARNING, "[Server] Operation not supported");
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server] server.sendResponse(GATT_REQUEST_NOT_SUPPORTED)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null);
mLogger.log(device, LogContract.Log.Level.VERBOSE, "[Server] Response sent");
}
@Override
public void onExecuteWrite(final BluetoothDevice device, final int requestId, final boolean execute) {
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server callback] Execute write request (requestId=" + requestId + ", execute=" + execute + ")");
// This method is not supported
mLogger.log(device, LogContract.Log.Level.WARNING, "[Server] Operation not supported");
mLogger.log(device, LogContract.Log.Level.DEBUG, "[Server] server.sendResponse(GATT_REQUEST_NOT_SUPPORTED)");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, 0, null);
mLogger.log(device, LogContract.Log.Level.VERBOSE, "[Server] Response sent");
}
};
/**
* Converts the connection state to String value
* @param state the connection state
* @return state as String
*/
private String stateToString(final int state) {
switch (state) {
case BluetoothProfile.STATE_CONNECTED:
return "CONNECTED";
case BluetoothProfile.STATE_CONNECTING:
return "CONNECTING";
case BluetoothProfile.STATE_DISCONNECTING:
return "DISCONNECTING";
default:
return "DISCONNECTED";
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package no.nordicsemi.android.nrftoolbox.proximity;
import android.bluetooth.BluetoothDevice;
import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
public interface ProximityServerManagerCallbacks extends BleManagerCallbacks {
void onAlarmTriggered(final BluetoothDevice device);
void onAlarmStopped(final BluetoothDevice device);
}

View File

@@ -34,50 +34,69 @@ import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.NotificationCompat;
import android.text.TextUtils;
import no.nordicsemi.android.log.Logger;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import no.nordicsemi.android.log.LogContract;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileService;
import no.nordicsemi.android.nrftoolbox.profile.multiconnect.BleMulticonnectProfileService;
public class ProximityService extends BleProfileService implements ProximityManagerCallbacks {
public class ProximityService extends BleMulticonnectProfileService implements ProximityManagerCallbacks, ProximityServerManagerCallbacks {
@SuppressWarnings("unused")
private static final String TAG = "ProximityService";
private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_DISCONNECT";
private final static String ACTION_FIND_ME = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_FIND_ME";
private final static String ACTION_SILENT_ME = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_SILENT_ME";
private final static String ACTION_FIND = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_FIND";
private final static String ACTION_SILENT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_SILENT";
private final static String EXTRA_DEVICE = "no.nordicsemi.android.nrftoolbox.proximity.EXTRA_DEVICE";
private ProximityManager mProximityManager;
private Ringtone mRingtoneNotification;
private Ringtone mRingtoneAlarm;
private boolean isImmediateAlertOn = false;
private final static int NOTIFICATION_ID = 100;
private final static String PROXIMITY_GROUP_ID = "proximity_connected_tags";
private final static int NOTIFICATION_ID = 1000;
private final static int OPEN_ACTIVITY_REQ = 0;
private final static int DISCONNECT_REQ = 1;
private final static int FIND_ME_REQ = 2;
private final static int SILENT_ME_REQ = 3;
private final static int FIND_REQ = 2;
private final static int SILENT_REQ = 3;
private final LocalBinder mBinder = new ProximityBinder();
private final ProximityBinder mBinder = new ProximityBinder();
private ProximityServerManager mServerManager;
private Ringtone mRingtoneAlarm;
/**
* When a device starts an alarm on the phone it is added to this list.
* Alarm is disabled when this list is empty.
*/
private List<BluetoothDevice> mDevicesWithAlarm;
/**
* This local binder is an interface for the bonded activity to operate with the proximity sensor
*/
public class ProximityBinder extends LocalBinder {
public boolean toggleImmediateAlert() {
if (isImmediateAlertOn) {
stopImmediateAlert();
} else {
startImmediateAlert();
}
return isImmediateAlertOn; // this value is changed by methods above
/**
* Toggles the Immediate Alert on given remote device.
* @param device the connected device
* @return true if alarm has been enabled, false if disabled
*/
public boolean toggleImmediateAlert(final BluetoothDevice device) {
final ProximityManager manager = (ProximityManager) getBleManager(device);
return manager.toggleImmediateAlert();
}
public boolean isImmediateAlertOn() {
return isImmediateAlertOn;
/**
* Returns the current alarm state on given device. This value is not read from the device, it's just the last value written to it
* (initially false).
* @param device the connected device
* @return true if alarm has been enabled, false if disabled
*/
public boolean isImmediateAlertOn(final BluetoothDevice device) {
final ProximityManager manager = (ProximityManager) getBleManager(device);
return manager.isAlertEnabled();
}
}
@@ -88,7 +107,7 @@ public class ProximityService extends BleProfileService implements ProximityMana
@Override
protected BleManager<ProximityManagerCallbacks> initializeManager() {
return mProximityManager = new ProximityManager(this);
return new ProximityManager(this);
}
/**
@@ -97,192 +116,322 @@ public class ProximityService extends BleProfileService implements ProximityMana
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
if (isConnected())
getBinder().disconnect();
else
stopSelf();
final BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] DISCONNECT action pressed");
mBinder.disconnect(device);
}
};
/**
* This broadcast receiver listens for {@link #ACTION_FIND_ME} that may be fired by pressing Find me action button on the notification.
* This broadcast receiver listens for {@link #ACTION_FIND} or {@link #ACTION_SILENT} that may be fired by pressing Find me action button on the notification.
*/
private final BroadcastReceiver mFindMeActionBroadcastReceiver = new BroadcastReceiver() {
private final BroadcastReceiver mToggleAlarmActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Find Me action pressed");
startImmediateAlert();
}
};
/**
* This broadcast receiver listens for {@link #ACTION_SILENT_ME} that may be fired by pressing Silent Me action button on the notification.
*/
private final BroadcastReceiver mSilentMeActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "[Notification] Silent Me action pressed");
stopImmediateAlert();
final BluetoothDevice device = intent.getParcelableExtra(EXTRA_DEVICE);
switch (intent.getAction()) {
case ACTION_FIND:
mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] FIND action pressed");
break;
case ACTION_SILENT:
mBinder.log(device, LogContract.Log.Level.INFO, "[Notification] SILENT action pressed");
break;
}
mBinder.toggleImmediateAlert(device);
createNotificationForConnectedDevice(device);
}
};
@Override
public void onCreate() {
super.onCreate();
protected void onServiceCreated() {
initializeAlarm();
registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
registerReceiver(mFindMeActionBroadcastReceiver, new IntentFilter(ACTION_FIND_ME));
registerReceiver(mSilentMeActionBroadcastReceiver, new IntentFilter(ACTION_SILENT_ME));
final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_FIND);
filter.addAction(ACTION_SILENT);
registerReceiver(mToggleAlarmActionBroadcastReceiver, filter);
}
@Override
public void onDestroy() {
cancelNotification();
unregisterReceiver(mDisconnectActionBroadcastReceiver);
unregisterReceiver(mFindMeActionBroadcastReceiver);
unregisterReceiver(mSilentMeActionBroadcastReceiver);
public void onServiceStopped() {
cancelNotifications();
super.onDestroy();
// GATT server might have not been created if Bluetooth was disabled
if (mServerManager != null) {
mServerManager.closeGattServer();
}
unregisterReceiver(mDisconnectActionBroadcastReceiver);
unregisterReceiver(mToggleAlarmActionBroadcastReceiver);
super.onServiceStopped();
}
@Override
protected void onBluetoothEnabled() {
super.onBluetoothEnabled();
// Start the GATT Server only if Bluetooth is enabled
mServerManager = new ProximityServerManager(this);
mServerManager.setLogger(mBinder);
mServerManager.openGattServer(this, new ProximityServerManager.OnServerOpenCallback() {
@Override
public void onGattServerOpen() {
// We are now ready to reconnect devices
ProximityService.super.onBluetoothEnabled();
}
});
}
@Override
protected void onBluetoothDisabled() {
super.onBluetoothDisabled();
if (mServerManager != null) {
mServerManager.closeGattServer();
}
}
@Override
protected void onRebind() {
// when the activity rebinds to the service, remove the notification
cancelNotification();
cancelNotifications();
}
@Override
public void onUnbind() {
// when the activity closes we need to show the notification that user is connected to the sensor
if (isConnected())
createNotification(R.string.proximity_notification_connected_message, 0);
else
createNotification(R.string.proximity_notification_linkloss_alert, 0);
createBackgroundNotification();
}
@Override
public void onDeviceDisconnecting(final BluetoothDevice device) {
stopAlarm();
}
public void onDeviceConnected(final BluetoothDevice device) {
super.onDeviceConnected(device);
@Override
public void onDeviceDisconnected(final BluetoothDevice device) {
super.onDeviceDisconnected(device);
isImmediateAlertOn = false;
if (!mBinded) {
createBackgroundNotification();
}
}
@Override
public void onLinklossOccur(final BluetoothDevice device) {
stopAlarm(device);
super.onLinklossOccur(device);
isImmediateAlertOn = false;
if (!mBinded) {
// when the activity closes we need to show the notification that user is connected to the sensor
playNotification();
createNotification(R.string.proximity_notification_linkloss_alert, Notification.DEFAULT_ALL);
createBackgroundNotification();
createLinklossNotification(device);
}
}
@Override
public void onDeviceDisconnected(final BluetoothDevice device) {
if (mServerManager != null) {
mServerManager.cancelConnection(device);
}
stopAlarm(device);
super.onDeviceDisconnected(device);
if (!mBinded) {
cancelNotification(device);
createBackgroundNotification();
}
}
@Override
public void onAlarmTriggered(final BluetoothDevice device) {
playAlarm();
playAlarm(device);
}
@Override
public void onAlarmStopped(final BluetoothDevice device) {
stopAlarm();
stopAlarm(device);
}
/**
* Creates the notification
*
* @param messageResId
* message resource id. The message must have one String parameter,<br />
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
* @param defaults
* signals that will be used to notify the user
*/
private void createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, ProximityActivity.class);
final Intent disconnect = new Intent(ACTION_DISCONNECT);
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent secondAction;
if (isImmediateAlertOn) {
final Intent intent = new Intent(ACTION_SILENT_ME);
secondAction = PendingIntent.getBroadcast(this, SILENT_ME_REQ, intent, PendingIntent.FLAG_UPDATE_CURRENT);
} else {
final Intent intent = new Intent(ACTION_FIND_ME);
secondAction = PendingIntent.getBroadcast(this, FIND_ME_REQ, intent, PendingIntent.FLAG_UPDATE_CURRENT);
private void createBackgroundNotification() {
final List<BluetoothDevice> connectedDevices = getConnectedDevices();
for (final BluetoothDevice device : connectedDevices) {
createNotificationForConnectedDevice(device);
}
createSummaryNotification();
}
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
builder.setSmallIcon(R.drawable.ic_stat_notify_proximity);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(defaults == 0); // an ongoing notification would not be shown on Android Wear
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.proximity_notification_action_disconnect), disconnectAction));
if (isConnected())
builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity, getString(isImmediateAlertOn ? R.string.proximity_action_silentme : R.string.proximity_action_findme), secondAction));
private void createSummaryNotification() {
final NotificationCompat.Builder builder = getNotificationBuilder();
builder.setColor(ContextCompat.getColor(this, R.color.actionBarColorDark));
builder.setShowWhen(false).setDefaults(0).setOngoing(true); // an ongoing notification will not be shown on Android Wear
builder.setGroup(PROXIMITY_GROUP_ID).setGroupSummary(true);
builder.setContentTitle(getString(R.string.app_name));
final List<BluetoothDevice> managedDevices = getManagedDevices();
final List<BluetoothDevice> connectedDevices = getConnectedDevices();
if (connectedDevices.isEmpty()) {
// No connected devices
final int numberOfManagedDevices = managedDevices.size();
if (numberOfManagedDevices == 1) {
final String name = getDeviceName(managedDevices.get(0));
builder.setContentText(getResources().getQuantityString(R.plurals.proximity_notification_text_nothing_connected, numberOfManagedDevices, name));
} else {
builder.setContentText(getResources().getQuantityString(R.plurals.proximity_notification_text_nothing_connected, numberOfManagedDevices, numberOfManagedDevices));
}
} else {
// There are some proximity tags connected
final StringBuilder text = new StringBuilder();
final int numberOfConnectedDevices = connectedDevices.size();
if (numberOfConnectedDevices == 1) {
final String name = getDeviceName(connectedDevices.get(0));
text.append(getResources().getQuantityString(R.plurals.proximity_notification_summary_text, numberOfConnectedDevices, name));
} else {
text.append(getResources().getQuantityString(R.plurals.proximity_notification_summary_text, numberOfConnectedDevices, numberOfConnectedDevices));
}
// If there are some disconnected devices, also print them
final int numberOfDisconnectedDevices = managedDevices.size() - numberOfConnectedDevices;
if (numberOfDisconnectedDevices == 1) {
text.append(", ");
// Find the single disconnected device to get its name
for (final BluetoothDevice device : managedDevices) {
if (!isConnected(device)) {
final String name = getDeviceName(device);
text.append(getResources().getQuantityString(R.plurals.proximity_notification_text_nothing_connected, numberOfDisconnectedDevices, name));
break;
}
}
} else if (numberOfDisconnectedDevices > 1) {
text.append(", ");
// If there are more, just write number of them
text.append(getResources().getQuantityString(R.plurals.proximity_notification_text_nothing_connected, numberOfDisconnectedDevices, numberOfDisconnectedDevices));
}
builder.setContentText(text);
}
final Notification notification = builder.build();
final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
nm.notify(NOTIFICATION_ID, notification);
}
/**
* Creates the notification for given connected device.
* Adds 3 action buttons: DISCONNECT, FIND and SILENT which perform given action on the device.
*/
private void createNotificationForConnectedDevice(final BluetoothDevice device) {
final NotificationCompat.Builder builder = getNotificationBuilder();
builder.setColor(ContextCompat.getColor(this, R.color.actionBarColorDark));
builder.setGroup(PROXIMITY_GROUP_ID).setDefaults(0).setOngoing(true); // an ongoing notification will not be shown on Android Wear
builder.setContentTitle(getString(R.string.proximity_notification_text, getDeviceName(device)));
// Add DISCONNECT action
final Intent disconnect = new Intent(ACTION_DISCONNECT);
disconnect.putExtra(EXTRA_DEVICE, device);
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ + device.hashCode(), disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_action_bluetooth, getString(R.string.proximity_action_disconnect), disconnectAction));
builder.setSortKey(getDeviceName(device) + device.getAddress()); // This will keep the same order of notification even after an action was clicked on one of them
// Add FIND or SILENT action
final ProximityManager manager = (ProximityManager) getBleManager(device);
if (manager.isAlertEnabled()) {
final Intent silentAllIntent = new Intent(ACTION_SILENT);
silentAllIntent.putExtra(EXTRA_DEVICE, device);
final PendingIntent silentAction = PendingIntent.getBroadcast(this, SILENT_REQ + device.hashCode(), silentAllIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity_silent, getString(R.string.proximity_action_silent), silentAction));
} else {
final Intent findAllIntent = new Intent(ACTION_FIND);
findAllIntent.putExtra(EXTRA_DEVICE, device);
final PendingIntent findAction = PendingIntent.getBroadcast(this, FIND_REQ + device.hashCode(), findAllIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(new NotificationCompat.Action(R.drawable.ic_stat_notify_proximity_find, getString(R.string.proximity_action_find), findAction));
}
final Notification notification = builder.build();
final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
nm.notify(device.getAddress(), NOTIFICATION_ID, notification);
}
/**
* Creates a notification showing information about a device that got disconnected.
*/
private void createLinklossNotification(final BluetoothDevice device) {
final NotificationCompat.Builder builder = getNotificationBuilder();
builder.setColor(ContextCompat.getColor(this, R.color.orange));
final Uri notificationUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
builder.setSound(notificationUri, AudioManager.STREAM_ALARM); // make sure the sound is played even in DND mode
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
builder.setCategory(NotificationCompat.CATEGORY_ALARM);
builder.setShowWhen(true).setOngoing(false); // an ongoing notification would not be shown on Android Wear
// This notification is to be shown not in a group
final String name = getDeviceName(device);
builder.setContentTitle(getString(R.string.proximity_notification_linkloss_alert, name));
builder.setTicker(getString(R.string.proximity_notification_linkloss_alert, name));
final Notification notification = builder.build();
final NotificationManagerCompat nm = NotificationManagerCompat.from(this);
nm.notify(device.getAddress(), NOTIFICATION_ID, notification);
}
private NotificationCompat.Builder getNotificationBuilder() {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, ProximityActivity.class);
// Both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed
final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(pendingIntent).setAutoCancel(false);
builder.setSmallIcon(R.drawable.ic_stat_notify_proximity);
return builder;
}
/**
* Cancels the existing notification. If there is no active notification this method does nothing
*/
private void cancelNotification() {
private void cancelNotifications() {
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID);
final List<BluetoothDevice> managedDevices = getManagedDevices();
for (final BluetoothDevice device : managedDevices) {
nm.cancel(device.getAddress(), NOTIFICATION_ID);
}
}
/**
* Cancels the existing notification for given device. If there is no active notification this method does nothing
*/
private void cancelNotification(final BluetoothDevice device) {
final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(device.getAddress(), NOTIFICATION_ID);
}
private void initializeAlarm() {
mDevicesWithAlarm = new LinkedList<>();
final Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
mRingtoneAlarm = RingtoneManager.getRingtone(this, alarmUri);
final Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
mRingtoneNotification = RingtoneManager.getRingtone(this, notification);
}
private void playNotification() {
mRingtoneNotification.play();
}
private void playAlarm(final BluetoothDevice device) {
final boolean alarmPlaying = !mDevicesWithAlarm.isEmpty();
if (!mDevicesWithAlarm.contains(device))
mDevicesWithAlarm.add(device);
private void playAlarm() {
final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
mRingtoneAlarm.play();
}
private void stopAlarm() {
mRingtoneAlarm.stop();
}
private void startImmediateAlert() {
isImmediateAlertOn = true;
mProximityManager.writeImmediateAlertOn();
if (!mBinded) {
createNotification(R.string.proximity_notification_connected_message, 0);
if (!alarmPlaying) {
final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
mRingtoneAlarm.play();
}
}
private void stopImmediateAlert() {
isImmediateAlertOn = false;
mProximityManager.writeImmediateAlertOff();
if (!mBinded) {
createNotification(R.string.proximity_notification_connected_message, 0);
private void stopAlarm(final BluetoothDevice device) {
mDevicesWithAlarm.remove(device);
if (mDevicesWithAlarm.isEmpty()) {
mRingtoneAlarm.stop();
}
}
private String getDeviceName(final BluetoothDevice device) {
String name = device.getName();
if (TextUtils.isEmpty(name))
name = getString(R.string.proximity_default_device_name);
return name;
}
}

View File

@@ -0,0 +1,111 @@
/*************************************************************************************************************************************************
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************************************************************************************/
package no.nordicsemi.android.nrftoolbox.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2016, Nordic Semiconductor
~ All rights reserved.
~
~ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
~
~ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
~
~ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~
~ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
~ software without specific prior written permission.
~
~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
~ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
~ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
~ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
~ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:maxLevel="7" android:drawable="@drawable/ic_battery_alert"/>
<item android:maxLevel="100" android:drawable="@drawable/ic_battery_full"/>
</level-list>

View File

@@ -48,68 +48,31 @@
android:textSize="32dp"
android:textStyle="bold"/>
<TextView
android:id="@+id/battery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginLeft="@dimen/feature_horizontal_margin"
android:layout_marginTop="@dimen/feature_device_name_margin_top"
android:background="@drawable/battery"
android:freezesText="true"
android:gravity="center"
android:text="@string/not_available"
android:textColor="#FFFFFF"
android:textSize="12sp"/>
<no.nordicsemi.android.nrftoolbox.widget.TrebuchetTextView
android:id="@+id/device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="@dimen/feature_device_name_margin_top"
android:layout_toRightOf="@+id/battery"
android:ellipsize="end"
android:freezesText="true"
android:maxLines="1"
android:text="@string/proximity_default_name"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<!-- Application section -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/option"
android:layout_below="@+id/device_name"
android:layout_centerHorizontal="true"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageLock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/proximity_lock_image_description"
android:src="@drawable/proximity_lock_closed"/>
<Button
android:id="@+id/action_findme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:enabled="false"
android:onClick="onFindMeClicked"
android:text="@string/proximity_action_findme"/>
</LinearLayout>
<CheckBox
android:id="@+id/option"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.List"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/feature_horizontal_margin"
android:layout_marginRight="@dimen/feature_horizontal_margin"
android:layout_marginTop="@dimen/feature_vertical_margin_top_narrow"
android:layout_marginBottom="@dimen/activity_vertical_margin_bottom_narrow"
android:layout_above="@+id/action_connect"
android:layout_centerHorizontal="true"
android:checked="true"
android:text="@string/proximity_enable_server"/>
android:layout_weight="1"
android:orientation="vertical">
<no.nordicsemi.android.nrftoolbox.widget.TrebuchetBoldTextView
style="@style/Widget.ListTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/proximity_devices_title"/>
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<Button
android:id="@+id/action_connect"
@@ -119,7 +82,7 @@
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/activity_vertical_margin_bottom"
android:onClick="onConnectClicked"
android:onClick="onAddDeviceClicked"
android:text="@string/action_connect"/>
<ImageView

View File

@@ -48,63 +48,31 @@
android:textSize="32dp"
android:textStyle="bold"/>
<TextView
android:id="@+id/battery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
<!-- Application section -->
<LinearLayout
style="@style/Widget.List"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/feature_horizontal_margin"
android:layout_marginTop="@dimen/feature_device_name_margin_top"
android:background="@drawable/battery"
android:freezesText="true"
android:gravity="center"
android:text="@string/not_available"
android:textColor="#FFFFFF"
android:textSize="12sp"/>
<no.nordicsemi.android.nrftoolbox.widget.TrebuchetTextView
android:id="@+id/device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="@dimen/feature_device_name_margin_top"
android:layout_toRightOf="@+id/battery"
android:ellipsize="end"
android:freezesText="true"
android:maxLines="1"
android:text="@string/proximity_default_name"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<ImageView
android:id="@+id/imageLock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/device_name"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:contentDescription="@string/proximity_lock_image_description"
android:src="@drawable/proximity_lock_closed"/>
<Button
android:id="@+id/action_findme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/imageLock"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
android:enabled="false"
android:onClick="onFindMeClicked"
android:text="@string/proximity_action_findme"/>
<CheckBox
android:id="@+id/option"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/feature_horizontal_margin"
android:layout_marginTop="@dimen/feature_vertical_margin_top_narrow"
android:layout_marginBottom="@dimen/activity_vertical_margin_bottom_narrow"
android:layout_above="@+id/action_connect"
android:layout_centerHorizontal="true"
android:checked="true"
android:text="@string/proximity_enable_server"/>
android:layout_weight="1"
android:orientation="vertical">
<no.nordicsemi.android.nrftoolbox.widget.TrebuchetBoldTextView
style="@style/Widget.ListTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/proximity_devices_title"/>
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<Button
android:id="@+id/action_connect"
@@ -114,8 +82,8 @@
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/activity_vertical_margin_bottom"
android:onClick="onConnectClicked"
android:text="@string/action_connect"/>
android:onClick="onAddDeviceClicked"
android:text="@string/action_add_device"/>
<ImageView
android:layout_width="wrap_content"

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2015, Nordic Semiconductor
~ All rights reserved.
~
~ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
~
~ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
~
~ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~
~ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
~ software without specific prior written permission.
~
~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
~ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
~ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
~ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
~ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:paddingBottom="2dp"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingTop="2dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_proximity_tag"
android:layout_marginRight="8dp"/>
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content">
<no.nordicsemi.android.nrftoolbox.widget.TrebuchetBoldTextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:lines="1"
android:textSize="20sp"
android:textColor="@android:color/black"/>
<TextView
android:id="@+id/address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:layout_marginTop="2dp"
android:layout_below="@+id/name"
android:lines="1"/>
<TextView
android:id="@+id/battery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:textSize="12sp"
android:gravity="center"
android:layout_toRightOf="@+id/address"
android:drawableLeft="@drawable/ic_battery"
android:layout_marginLeft="8dp"
android:visibility="gone"/>
</RelativeLayout>
<ImageButton
android:id="@+id/action_find_silent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/actionBarColorDark"
android:src="@drawable/ic_stat_notify_proximity_find"
android:visibility="gone"/>
<ImageButton
android:id="@+id/action_disconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/orange"
android:src="@drawable/ic_action_disconnect"/>
</LinearLayout>

View File

@@ -31,6 +31,8 @@
<dimen name="navdrawer_padding_horiz">24dp</dimen>
<dimen name="feature_vertical_margin_top">80dp</dimen>
<dimen name="feature_vertical_margin_top_narrow">50dp</dimen>
<dimen name="activity_vertical_margin_bottom_narrow">42dp</dimen>
<dimen name="feature_horizontal_margin">80dp</dimen>
<dimen name="feature_device_name_margin_top">16dp</dimen>
<dimen name="feature_grid_margin_top">20dp</dimen>

View File

@@ -24,8 +24,10 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin_bottom">32dp</dimen>
<dimen name="activity_vertical_margin_bottom_narrow">16dp</dimen>
<dimen name="feature_horizontal_margin">50dp</dimen>
<dimen name="feature_vertical_margin_top">50dp</dimen>
<dimen name="feature_vertical_margin_top_narrow">26dp</dimen>
<dimen name="feature_section_padding">8dp</dimen>
<dimen name="feature_device_name_margin_top">8dp</dimen>
<dimen name="feature_grid_margin_top">0dp</dimen>

View File

@@ -35,6 +35,7 @@
<string name="action_settings">Settings</string>
<string name="action_connect">CONNECT</string>
<string name="action_select">SELECT DEVICE</string>
<string name="action_add_device">ADD DEVICE</string>
<string name="action_connecting">CONNECTING…</string>
<string name="action_disconnect">DISCONNECT</string>

View File

@@ -21,20 +21,34 @@
~ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<resources>
<string name="proximity_feature_title">PROXIMITY</string>
<string name="proximity_feature_title_long">PROXIMITY MONITOR</string>
<dimen name="proximity_feature_title_long_margin">-142dp</dimen>
<string name="proximity_default_name">DEFAULT PROXIMITY</string>
<string name="proximity_action_findme">Find Me</string>
<string name="proximity_action_silentme">Silent Me</string>
<string name="proximity_lock_image_description">LOCK Image</string>
<string name="proximity_notification_action_disconnect">Disconnect</string>
<string name="proximity_notification_linkloss_alert">%s is getting away!</string>
<string name="proximity_notification_connected_message">%s is connected.</string>
<string name="proximity_enable_server">GATT Server enabled</string>
<string name="proximity_feature_title">PROXIMITY</string>
<string name="proximity_feature_title_long">PROXIMITY MONITOR</string>
<dimen name="proximity_feature_title_long_margin">-142dp</dimen>
<string name="proximity_action_disconnect">Disconnect</string>
<string name="proximity_action_remove">Remove</string>
<string name="proximity_action_find">Find</string>
<string name="proximity_action_silent">Silent</string>
<string name="proximity_default_device_name">Proximity Tag</string>
<plurals name="proximity_notification_text_nothing_connected">
<item quantity="one">%s is disconnected.</item>
<item quantity="few">%d tags are disconnected.</item>
<item quantity="many">%d tags are disconnected.</item>
<item quantity="other">%d tags are disconnected.</item>
</plurals>
<string name="proximity_notification_text">%s connected</string>
<plurals name="proximity_notification_summary_text">
<item quantity="one">%s is connected.</item>
<item quantity="few">%d tags connected.</item>
<item quantity="many">%d tags connected.</item>
<item quantity="other">%d tags connected.</item>
</plurals>
<string name="proximity_notification_linkloss_alert">%s is getting away!</string>
<string name="proximity_devices_title">YOUR TAGS</string>
<string name="proximity_enable_server">GATT Server enabled</string>
<string name="proximity_about_text">PROXIMITY profile allows you to connect to your Proximity sensor.
\nYou can find your valuables attached with Proximity tag by pressing FindMe button on screen and you can find your phone by pressing relevant button on your tag.