initial version

This commit is contained in:
Harald Hoyer 2017-04-24 16:08:30 +02:00 committed by Harald Hoyer
commit a0d3e184f9
18 changed files with 1547 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

Binary file not shown.

View file

@ -0,0 +1,2 @@
configurations.maybeCreate("default")
artifacts.add("default", file('android_antlib_4-14-0.jar'))

1
app/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

32
app/build.gradle Normal file
View file

@ -0,0 +1,32 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "xyz.hoyer.iconsole"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:support-v4:24.2.1'
testCompile 'junit:junit:4.12'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
provided project(':android_antlib_4-14-0')
}

25
app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/harald/Android/Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz.hoyer.iconsole">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".ChannelList"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name"
android:theme="@style/FullscreenTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".ChannelService"></service>
</application>
</manifest>

View file

@ -0,0 +1,40 @@
package com.dsi.ant.channel;
/**
* Created by harald on 24.04.17.
*/
public enum PredefinedNetwork {
INVALID(-1),
PUBLIC(0),
ANT_PLUS1(1), //
ANT_FS(2);
private final int mRawValue;
private static final PredefinedNetwork[] sValues = values();
private PredefinedNetwork(int rawValue) {
this.mRawValue = rawValue;
}
int getRawValue() {
return this.mRawValue;
}
private boolean equals(int rawValue) {
return rawValue == this.mRawValue;
}
static PredefinedNetwork create(int rawValue) {
PredefinedNetwork code = INVALID;
for(int i = 0; i < sValues.length; ++i) {
if(sValues[i].equals(rawValue)) {
code = sValues[i];
break;
}
}
return code;
}
}

View file

@ -0,0 +1,296 @@
/*
* Copyright 2012 Dynastream Innovations Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package xyz.hoyer.iconsole;
import android.os.RemoteException;
import android.util.Log;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
import com.dsi.ant.channel.IAntChannelEventHandler;
import com.dsi.ant.message.ChannelId;
import com.dsi.ant.message.ChannelType;
import com.dsi.ant.message.fromant.AcknowledgedDataMessage;
import com.dsi.ant.message.fromant.BroadcastDataMessage;
import com.dsi.ant.message.fromant.ChannelEventMessage;
import com.dsi.ant.message.fromant.MessageFromAntType;
import com.dsi.ant.message.ipc.AntMessageParcel;
import java.util.Random;
public class ChannelController
{
// The device type and transmission type to be part of the channel ID message
private static final int CHANNEL_PROOF_DEVICE_TYPE = 0x08;
private static final int CHANNEL_PROOF_TRANSMISSION_TYPE = 1;
// The period and frequency values the channel will be configured to
private static final int CHANNEL_PROOF_PERIOD = 32768; // 1 Hz
private static final int CHANNEL_PROOF_FREQUENCY = 77;
private static final String TAG = ChannelController.class.getSimpleName();
private static Random randGen = new Random();
private AntChannel mAntChannel;
private ChannelBroadcastListener mChannelBroadcastListener;
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
private ChannelInfo mChannelInfo;
private boolean mIsOpen;
static public abstract class ChannelBroadcastListener
{
public abstract void onBroadcastChanged(ChannelInfo newInfo);
}
public ChannelController(AntChannel antChannel, boolean isMaster, int deviceId,
ChannelBroadcastListener broadcastListener)
{
mAntChannel = antChannel;
mChannelInfo = new ChannelInfo(deviceId, isMaster, randGen.nextInt(256));
mChannelBroadcastListener = broadcastListener;
openChannel();
}
boolean openChannel()
{
if(null != mAntChannel)
{
if(mIsOpen)
{
Log.w(TAG, "Channel was already open");
}
else
{
/*
* Although this reference code sets ChannelType to either a transmitting master or a receiving slave,
* the standard for ANT is that channels communication is bidirectional. The use of single-direction
* communication in this app is for ease of understanding as reference code. For more information and
* any additional features on ANT channel communication, refer to the ANT Protocol Doc found at:
* http://www.thisisant.com/resources/ant-message-protocol-and-usage/
*/
ChannelType channelType = (mChannelInfo.isMaster ?
ChannelType.BIDIRECTIONAL_MASTER : ChannelType.BIDIRECTIONAL_SLAVE);
// Channel ID message contains device number, type and transmission type. In
// order for master (TX) channels and slave (RX) channels to connect, they
// must have the same channel ID, or wildcard (0) is used.
ChannelId channelId = new ChannelId(mChannelInfo.deviceNumber,
CHANNEL_PROOF_DEVICE_TYPE, CHANNEL_PROOF_TRANSMISSION_TYPE);
try
{
// Setting the channel event handler so that we can receive messages from ANT
mAntChannel.setChannelEventHandler(mChannelEventCallback);
// Performs channel assignment by assigning the type to the channel. Additional
// features (such as, background scanning and frequency agility) can be enabled
// by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
mAntChannel.assign(channelType);
/*
* Configures the channel ID, messaging period and rf frequency after assigning,
* then opening the channel.
*
* For any additional ANT features such as proximity search or background scanning, refer to
* the ANT Protocol Doc found at:
* http://www.thisisant.com/resources/ant-message-protocol-and-usage/
*/
mAntChannel.setChannelId(channelId);
mAntChannel.setPeriod(CHANNEL_PROOF_PERIOD);
mAntChannel.setRfFrequency(CHANNEL_PROOF_FREQUENCY);
mAntChannel.open();
mIsOpen = true;
Log.d(TAG, "Opened channel with device number: " + mChannelInfo.deviceNumber);
} catch (RemoteException e) {
channelError(e);
} catch (AntCommandFailedException e) {
// This will release, and therefore unassign if required
channelError("Open failed", e);
}
}
}
else
{
Log.w(TAG, "No channel available");
}
return mIsOpen;
}
/**
* Implements the Channel Event Handler Interface so that messages can be
* received and channel death events can be handled.
*/
public class ChannelEventCallback implements IAntChannelEventHandler
{
private void updateData(byte[] data) {
mChannelInfo.broadcastData = data;
mChannelBroadcastListener.onBroadcastChanged(mChannelInfo);
}
@Override
public void onChannelDeath()
{
// Display channel death message when channel dies
displayChannelError("Channel Death");
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
Log.d(TAG, "Rx: "+ antParcel);
// Switching on message type to handle different types of messages
switch(messageType)
{
// If data message, construct from parcel and update channel data
case BROADCAST_DATA:
// Rx Data
updateData(new BroadcastDataMessage(antParcel).getPayload());
break;
case ACKNOWLEDGED_DATA:
// Rx Data
updateData(new AcknowledgedDataMessage(antParcel).getPayload());
break;
case CHANNEL_EVENT:
// Constructing channel event message from parcel
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
// Switching on event code to handle the different types of channel events
switch(eventMessage.getEventCode())
{
case TX:
// Use old info as this is what remote device has just received
mChannelBroadcastListener.onBroadcastChanged(mChannelInfo);
mChannelInfo.broadcastData[0]++;
if(mIsOpen)
{
try {
// Setting the data to be broadcast on the next channel period
mAntChannel.setBroadcastData(mChannelInfo.broadcastData);
} catch (RemoteException e) {
channelError(e);
}
}
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching
displayChannelError("No Device Found");
break;
case CHANNEL_CLOSED:
case CHANNEL_COLLISION:
case RX_FAIL:
case RX_FAIL_GO_TO_SEARCH:
case TRANSFER_RX_FAILED:
case TRANSFER_TX_COMPLETED:
case TRANSFER_TX_FAILED:
case TRANSFER_TX_START:
case UNKNOWN:
// TODO More complex communication will need to handle these events
break;
}
break;
case ANT_VERSION:
case BURST_TRANSFER_DATA:
case CAPABILITIES:
case CHANNEL_ID:
case CHANNEL_RESPONSE:
case CHANNEL_STATUS:
case SERIAL_NUMBER:
case OTHER:
// TODO More complex communication will need to handle these message types
break;
}
}
}
public ChannelInfo getCurrentInfo()
{
return mChannelInfo;
}
void displayChannelError(String displayText)
{
mChannelInfo.die(displayText);
mChannelBroadcastListener.onBroadcastChanged(mChannelInfo);
}
void channelError(RemoteException e) {
String logString = "Remote service communication failed.";
Log.e(TAG, logString);
displayChannelError(logString);
}
void channelError(String error, AntCommandFailedException e) {
StringBuilder logString;
if(e.getResponseMessage() != null) {
String initiatingMessageId = "0x"+ Integer.toHexString(
e.getResponseMessage().getInitiatingMessageId());
String rawResponseCode = "0x"+ Integer.toHexString(
e.getResponseMessage().getRawResponseCode());
logString = new StringBuilder(error)
.append(". Command ")
.append(initiatingMessageId)
.append(" failed with code ")
.append(rawResponseCode);
} else {
String attemptedMessageId = "0x"+ Integer.toHexString(
e.getAttemptedMessageType().getMessageId());
String failureReason = e.getFailureReason().toString();
logString = new StringBuilder(error)
.append(". Command ")
.append(attemptedMessageId)
.append(" failed with reason ")
.append(failureReason);
}
Log.e(TAG, logString.toString());
mAntChannel.release();
displayChannelError("ANT Command Failed");
}
public void close()
{
// TODO kill all our resources
if (null != mAntChannel)
{
mIsOpen = false;
// Releasing the channel to make it available for others.
// After releasing, the AntChannel instance cannot be reused.
mAntChannel.release();
mAntChannel = null;
}
displayChannelError("Channel Closed");
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2012 Dynastream Innovations Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package xyz.hoyer.iconsole;
import com.dsi.ant.message.fromant.DataMessage;
public class ChannelInfo
{
public final int deviceNumber;
/** Master / Slave */
public final boolean isMaster;
public byte[] broadcastData = new byte[DataMessage.LENGTH_STANDARD_PAYLOAD];
public boolean error;
private String mErrorMessage;
public ChannelInfo(int deviceNumber, boolean isMaster, int initialBroadcastValue)
{
this.deviceNumber = deviceNumber;
this.isMaster = isMaster;
// Not actually concerned with this value, so can cast to byte and lose data without issues
broadcastData[0] = (byte)initialBroadcastValue;
error = false;
mErrorMessage = null;
}
public void die(String errorMessage)
{
error = true;
mErrorMessage = errorMessage;
}
public String getErrorString()
{
return mErrorMessage;
}
}

View file

@ -0,0 +1,387 @@
/*
* Copyright 2012 Dynastream Innovations Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package xyz.hoyer.iconsole;
import com.dsi.ant.channel.ChannelNotAvailableException;
import xyz.hoyer.iconsole.ChannelService.ChannelChangedListener;
import xyz.hoyer.iconsole.ChannelService.ChannelServiceComm;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.ToggleButton;
import java.util.ArrayList;
public class ChannelList extends Activity {
private static final String TAG = ChannelList.class.getSimpleName();
private final String PREF_TX_BUTTON_CHECKED_KEY = "ChannelList.TX_BUTTON_CHECKED";
private boolean mCreateChannelAsMaster;
private ChannelServiceComm mChannelService;
private ArrayList<String> mChannelDisplayList = new ArrayList<String>();
private ArrayAdapter<String> mChannelListAdapter;
private SparseArray<Integer> mIdChannelListIndexMap = new SparseArray<Integer>();
private boolean mChannelServiceBound = false;
private void initButtons()
{
Log.v(TAG, "initButtons...");
//Register Master/Slave Toggle handler
ToggleButton toggleButton_MasterSlave = (ToggleButton)findViewById(R.id.toggleButton_MasterSlave);
toggleButton_MasterSlave.setEnabled(mChannelServiceBound);
toggleButton_MasterSlave.setChecked(mCreateChannelAsMaster);
toggleButton_MasterSlave.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
{
@Override
public void onCheckedChanged(CompoundButton arg0, boolean enabled)
{
mCreateChannelAsMaster = enabled;
}
});
//Register Add Channel Button handler
Button button_addChannel = (Button)findViewById(R.id.button_AddChannel);
button_addChannel.setEnabled(mChannelServiceBound);
button_addChannel.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
addNewChannel(mCreateChannelAsMaster);
}
});
//Register Clear Channels Button handler
Button button_clearChannels = (Button)findViewById(R.id.button_ClearChannels);
button_clearChannels.setEnabled(mChannelServiceBound);
button_clearChannels.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
clearAllChannels();
}
});
Log.v(TAG, "...initButtons");
}
private void initPrefs()
{
Log.v(TAG, "initPrefs...");
// Retrieves the app's current state of channel transmission mode
// from preferences to handle app resuming.
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
mCreateChannelAsMaster = preferences.getBoolean(PREF_TX_BUTTON_CHECKED_KEY, true);
Log.v(TAG, "...initPrefs");
}
private void savePrefs()
{
Log.v(TAG, "savePrefs...");
// Saves the app's current state of channel transmission mode to preferences
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(PREF_TX_BUTTON_CHECKED_KEY, mCreateChannelAsMaster);
editor.commit();
Log.v(TAG, "...savePrefs");
}
private void doBindChannelService()
{
Log.v(TAG, "doBindChannelService...");
// Binds to ChannelService. ChannelService binds and manages connection between the
// app and the ANT Radio Service
Intent bindIntent = new Intent(this, ChannelService.class);
startService(bindIntent);
mChannelServiceBound = bindService(bindIntent, mChannelServiceConnection, Context.BIND_AUTO_CREATE);
if(!mChannelServiceBound) //If the bind returns false, run the unbind method to update the GUI
doUnbindChannelService();
Log.i(TAG, " Channel Service binding = "+ mChannelServiceBound);
Log.v(TAG, "...doBindChannelService");
}
private void doUnbindChannelService()
{
Log.v(TAG, "doUnbindChannelService...");
if(mChannelServiceBound)
{
unbindService(mChannelServiceConnection);
mChannelServiceBound = false;
}
((Button)findViewById(R.id.button_ClearChannels)).setEnabled(false);
((Button)findViewById(R.id.button_AddChannel)).setEnabled(false);
((Button)findViewById(R.id.toggleButton_MasterSlave)).setEnabled(false);
Log.v(TAG, "...doUnbindChannelService");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v(TAG, "onCreate...");
mChannelServiceBound = false;
setContentView(R.layout.activity_fullscreen);
initPrefs();
mChannelListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, mChannelDisplayList);
ListView listView_channelList = (ListView)findViewById(R.id.listView_channelList);
listView_channelList.setAdapter(mChannelListAdapter);
if(!mChannelServiceBound) doBindChannelService();
initButtons();
Log.v(TAG, "...onCreate");
}
public void onBack() {
finish();
}
@Override
public void onDestroy()
{
Log.v(TAG, "onDestroy...");
doUnbindChannelService();
if(isFinishing())
{
stopService(new Intent(this, ChannelService.class));
}
mChannelServiceConnection = null;
savePrefs();
Log.v(TAG, "...onDestroy");
super.onDestroy();
}
private ServiceConnection mChannelServiceConnection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder serviceBinder)
{
Log.v(TAG, "mChannelServiceConnection.onServiceConnected...");
mChannelService = (ChannelServiceComm) serviceBinder;
// Sets a listener that handles channel events
mChannelService.setOnChannelChangedListener(new ChannelChangedListener()
{
// Occurs when a channel has new info/data
@Override
public void onChannelChanged(final ChannelInfo newInfo)
{
Integer index = mIdChannelListIndexMap.get(newInfo.deviceNumber);
if(null != index && index.intValue() < mChannelDisplayList.size())
{
mChannelDisplayList.set(index.intValue(), getDisplayText(newInfo));
runOnUiThread(new Runnable()
{
@Override
public void run()
{
mChannelListAdapter.notifyDataSetChanged();
}
});
}
}
// Updates the UI to allow/disallow acquiring new channels
@Override
public void onAllowAddChannel(boolean addChannelAllowed) {
// Enable Add Channel button and Master/Slave toggle if
// adding channels is allowed
((Button)findViewById(R.id.button_AddChannel)).setEnabled(addChannelAllowed);
((Button)findViewById(R.id.toggleButton_MasterSlave)).setEnabled(addChannelAllowed);
}
});
// Initial check when connecting to ChannelService if adding channels is allowed
boolean allowAcquireChannel = mChannelService.isAddChannelAllowed();
((Button)findViewById(R.id.button_AddChannel)).setEnabled(allowAcquireChannel);
((Button)findViewById(R.id.toggleButton_MasterSlave)).setEnabled(allowAcquireChannel);
refreshList();
Log.v(TAG, "...mChannelServiceConnection.onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName arg0)
{
Log.v(TAG, "mChannelServiceConnection.onServiceDisconnected...");
// Clearing and disabling when disconnecting from ChannelService
mChannelService = null;
((Button)findViewById(R.id.button_ClearChannels)).setEnabled(false);
((Button)findViewById(R.id.button_AddChannel)).setEnabled(false);
((Button)findViewById(R.id.toggleButton_MasterSlave)).setEnabled(false);
Log.v(TAG, "...mChannelServiceConnection.onServiceDisconnected");
}
};
// This method is called when 'Add Channel' button is clicked
private void addNewChannel(final boolean isMaster)
{
Log.v(TAG, "addNewChannel...");
if(null != mChannelService)
{
ChannelInfo newChannelInfo;
try
{
// Telling the ChannelService to add a new channel. This method
// in ChannelService contains code required to acquire an ANT
// channel from ANT Radio Service.
newChannelInfo = mChannelService.addNewChannel(isMaster);
} catch (ChannelNotAvailableException e)
{
// Occurs when a channel is not available. Printing out the
// stack trace will show why no channels are available.
Toast.makeText(this, "Channel Not Available", Toast.LENGTH_SHORT).show();
return;
}
if(null != newChannelInfo)
{
// Adding new channel info to the list
addChannelToList(newChannelInfo);
mChannelListAdapter.notifyDataSetChanged();
}
}
Log.v(TAG, "...addNewChannel");
}
private void refreshList()
{
Log.v(TAG, "refreshList...");
if(null != mChannelService)
{
ArrayList<ChannelInfo> chInfoList = mChannelService.getCurrentChannelInfoForAllChannels();
mChannelDisplayList.clear();
for(ChannelInfo i: chInfoList)
{
addChannelToList(i);
}
mChannelListAdapter.notifyDataSetChanged();
}
Log.v(TAG, "...refreshList");
}
private void addChannelToList(ChannelInfo channelInfo)
{
Log.v(TAG, "addChannelToList...");
mIdChannelListIndexMap.put(channelInfo.deviceNumber, mChannelDisplayList.size());
mChannelDisplayList.add(getDisplayText(channelInfo));
Log.v(TAG, "...addChannelToList");
}
private static String getDisplayText(ChannelInfo channelInfo)
{
Log.v(TAG, "getDisplayText...");
String displayText = null;
if(channelInfo.error)
{
displayText = String.format("#%-6d !:%s", channelInfo.deviceNumber, channelInfo.getErrorString());
}
else
{
if(channelInfo.isMaster)
{
displayText = String.format("#%-6d Tx:[%2d]", channelInfo.deviceNumber, channelInfo.broadcastData[0] & 0xFF);
}
else
{
displayText = String.format("#%-6d Rx:[%2d]", channelInfo.deviceNumber, channelInfo.broadcastData[0] & 0xFF);
}
}
Log.v(TAG, "...getDisplayText");
return displayText;
}
private void clearAllChannels()
{
Log.v(TAG, "clearAllChannels...");
if(null != mChannelService)
{
// Telling ChannelService to close all the channels
mChannelService.clearAllChannels();
mChannelDisplayList.clear();
mIdChannelListIndexMap.clear();
mChannelListAdapter.notifyDataSetChanged();
}
Log.v(TAG, "...clearAllChannels");
}
}

View file

@ -0,0 +1,379 @@
/*
* Copyright 2012 Dynastream Innovations Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package xyz.hoyer.iconsole;
import xyz.hoyer.iconsole.ChannelController.ChannelBroadcastListener;
import com.dsi.ant.AntService;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntChannelProvider;
import com.dsi.ant.channel.ChannelNotAvailableException;
import com.dsi.ant.channel.PredefinedNetwork;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import java.util.ArrayList;
public class ChannelService extends Service
{
private static final String TAG = "ChannelService";
private Object mCreateChannel_LOCK = new Object();
SparseArray<ChannelController> mChannelControllerList = new SparseArray<ChannelController>();
ChannelChangedListener mListener;
int channelDeviceIdCounter = 0;
private boolean mAntRadioServiceBound;
private AntService mAntRadioService = null;
private AntChannelProvider mAntChannelProvider = null;
private boolean mAllowAddChannel = false;
private ServiceConnection mAntRadioServiceConnection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
// Must pass in the received IBinder object to correctly construct an AntService object
mAntRadioService = new AntService(service);
try {
// Getting a channel provider in order to acquire channels
mAntChannelProvider = mAntRadioService.getChannelProvider();
// Initial check for number of channels available
boolean mChannelAvailable = mAntChannelProvider.getNumChannelsAvailable() > 0;
// Initial check for if legacy interface is in use. If the
// legacy interface is in use, applications can free the ANT
// radio by attempting to acquire a channel.
boolean legacyInterfaceInUse = mAntChannelProvider.isLegacyInterfaceInUse();
// If there are channels OR legacy interface in use, allow adding channels
if(mChannelAvailable || legacyInterfaceInUse) {
mAllowAddChannel = true;
}
else {
// If no channels available AND legacy interface is not in use, disallow adding channels
mAllowAddChannel = false;
}
if(mAllowAddChannel) {
if(null != mListener) {
// Send an event that indicates if adding channels is allowed
mListener.onAllowAddChannel(mAllowAddChannel);
}
}
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name)
{
die("Binder Died");
mAntChannelProvider = null;
mAntRadioService = null;
if(mAllowAddChannel) { mListener.onAllowAddChannel(false); }
mAllowAddChannel = false;
}
};
public interface ChannelChangedListener
{
/**
* Occurs when a Channel's Info has changed (i.e. a newly created
* channel, channel has transmitted or received data, or if channel has
* been closed.
*
* @param newInfo The channel's updated info
*/
void onChannelChanged(ChannelInfo newInfo);
/**
* Occurs when there is adding a channel is being allowed or disallowed.
*
* @param addChannelAllowed True if adding channels is allowed. False, otherwise.
*/
void onAllowAddChannel(boolean addChannelAllowed);
}
/**
* The interface used to communicate with the ChannelService
*/
public class ChannelServiceComm extends Binder
{
/**
* Sets the listener to be used for channel changed event callbacks.
*
* @param listener The listener that will receive events
*/
void setOnChannelChangedListener(ChannelChangedListener listener)
{
mListener = listener;
}
/**
* Retrieves the current info for all channels currently added.
*
* @return A list that contains info for all the channels
*/
ArrayList<ChannelInfo> getCurrentChannelInfoForAllChannels()
{
ArrayList<ChannelInfo> retList = new ArrayList<ChannelInfo>();
for(int i = 0; i < mChannelControllerList.size(); i++)
{
ChannelController channel = mChannelControllerList.valueAt(i);
retList.add(channel.getCurrentInfo());
}
return retList;
}
/**
* Acquires and adds a channel from ANT Radio Service
*
* @param isMaster True if channel is transmitting, False if channel is receiving
* @return The info for the newly acquired and added channel
* @throws ChannelNotAvailableException
*/
ChannelInfo addNewChannel(final boolean isMaster) throws ChannelNotAvailableException
{
return createNewChannel(isMaster);
}
/**
* Closes all channels currently added.
*/
void clearAllChannels() { closeAllChannels(); }
/**
* Queries if adding a channel is allowed.
* @return True if adding a channel is allowed. False, otherwise.
*/
boolean isAddChannelAllowed() { return mAllowAddChannel; }
}
private void closeAllChannels()
{
synchronized (mChannelControllerList)
{
// Closing all channels in the list
for(int i = 0; i < mChannelControllerList.size(); i++)
{
mChannelControllerList.valueAt(i).close();
}
mChannelControllerList.clear();
}
// Reset the device id counter
channelDeviceIdCounter = 0;
}
AntChannel acquireChannel() throws ChannelNotAvailableException
{
AntChannel mAntChannel = null;
if(null != mAntChannelProvider)
{
try
{
/*
* If applications require a channel with specific capabilities
* (event buffering, background scanning etc.), a Capabilities
* object should be created and then the specific capabilities
* required set to true. Applications can specify both required
* and desired Capabilities with both being passed in
* acquireChannel(context, PredefinedNetwork,
* requiredCapabilities, desiredCapabilities).
*/
mAntChannel = mAntChannelProvider.acquireChannel(this, PredefinedNetwork.ANT_PLUS1);
/*
NetworkKey mNK = new NetworkKey(new byte[] { (byte)0xb9, (byte)0xa5, (byte)0x21, (byte)0xfb,
(byte)0xbd, (byte)0x72, (byte)0xc3, (byte)0x45 });
Log.v(TAG, mNK.toString());
mAntChannel = mAntChannelProvider.acquireChannelOnPrivateNetwork(this, mNK);
*/
} catch (RemoteException e)
{
die("ACP Remote Ex");
}
}
return mAntChannel;
}
public ChannelInfo createNewChannel(final boolean isMaster) throws ChannelNotAvailableException
{
ChannelController channelController = null;
synchronized(mCreateChannel_LOCK)
{
// Acquiring a channel from ANT Radio Service
AntChannel antChannel = acquireChannel();
if(null != antChannel)
{
channelDeviceIdCounter += 1;
// Constructing a controller that will manage and control the channel
channelController = new ChannelController(antChannel, isMaster, channelDeviceIdCounter,
new ChannelBroadcastListener()
{
@Override
public void onBroadcastChanged(ChannelInfo newInfo)
{
// Sending a channel changed event when message from ANT is received
mListener.onChannelChanged(newInfo);
}
});
mChannelControllerList.put(channelDeviceIdCounter, channelController);
}
}
if(null == channelController) return null;
return channelController.getCurrentInfo();
}
@Override
public IBinder onBind(Intent arg0)
{
return new ChannelServiceComm();
}
/**
* Receives AntChannelProvider state changes being sent from ANT Radio Service
*/
private final BroadcastReceiver mChannelProviderStateChangedReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED.equals(intent.getAction())) {
boolean update = false;
// Retrieving the data contained in the intent
int numChannels = intent.getIntExtra(AntChannelProvider.NUM_CHANNELS_AVAILABLE, 0);
boolean legacyInterfaceInUse = intent.getBooleanExtra(AntChannelProvider.LEGACY_INTERFACE_IN_USE, false);
if(mAllowAddChannel) {
// Was a acquire channel allowed
// If no channels available AND legacy interface is not in use, disallow acquiring of channels
if(0 == numChannels && !legacyInterfaceInUse) {
mAllowAddChannel = false;
update = true;
}
} else {
// Acquire channels not allowed
// If there are channels OR legacy interface in use, allow acquiring of channels
if(numChannels > 0 || legacyInterfaceInUse) {
mAllowAddChannel = true;
update = true;
}
}
if(update && (null != mListener)) {
// AllowAddChannel has been changed, sending event callback
mListener.onAllowAddChannel(mAllowAddChannel);
}
}
}
};
private void doBindAntRadioService()
{
if(BuildConfig.DEBUG) Log.v(TAG, "doBindAntRadioService");
// Start listing for channel available intents
registerReceiver(mChannelProviderStateChangedReceiver, new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED));
// Creating the intent and calling context.bindService() is handled by
// the static bindService() method in AntService
mAntRadioServiceBound = AntService.bindService(this, mAntRadioServiceConnection);
}
private void doUnbindAntRadioService()
{
if(BuildConfig.DEBUG) Log.v(TAG, "doUnbindAntRadioService");
// Stop listing for channel available intents
try{
unregisterReceiver(mChannelProviderStateChangedReceiver);
} catch (IllegalArgumentException exception) {
if(BuildConfig.DEBUG) Log.d(TAG, "Attempting to unregister a never registered Channel Provider State Changed receiver.");
}
if(mAntRadioServiceBound)
{
try
{
unbindService(mAntRadioServiceConnection);
}
catch(IllegalArgumentException e)
{
// Not bound, that's what we want anyway
}
mAntRadioServiceBound = false;
}
}
@Override
public void onCreate()
{
super.onCreate();
mAntRadioServiceBound = false;
doBindAntRadioService();
}
@Override
public void onDestroy()
{
closeAllChannels();
doUnbindAntRadioService();
mAntChannelProvider = null;
super.onDestroy();
}
static void die(String error)
{
Log.e(TAG, "DIE: "+ error);
}
}

23
build.gradle Normal file
View file

@ -0,0 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

17
gradle.properties Normal file
View file

@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

160
gradlew vendored Executable file
View file

@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View file

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
settings.gradle Normal file
View file

@ -0,0 +1 @@
include ':app', ':android_antlib_4-14-0'