This commit is contained in:
Harald Hoyer 2017-04-27 15:09:59 +02:00
parent d2e60c858f
commit 39af7e815e
19 changed files with 593 additions and 1059 deletions

View file

@ -36,7 +36,7 @@ android {
buildToolsVersion "25.0.2" buildToolsVersion "25.0.2"
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 16
targetSdkVersion 25 targetSdkVersion 25
} }

View file

@ -47,7 +47,6 @@
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/select_device" android:label="@string/select_device"
android:theme="@android:style/Theme.Holo.Dialog"/> android:theme="@android:style/Theme.Holo.Dialog"/>
<service android:name=".BluetoothChatService"></service>
</application> </application>
</manifest> </manifest>

View file

@ -16,37 +16,33 @@
package org.surfsite.iconsole; package org.surfsite.iconsole;
import android.annotation.SuppressLint;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.Activity; import android.app.Activity;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder;
import android.os.Message; import android.os.Message;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.NumberPicker; import android.widget.NumberPicker;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.util.Log;
import org.surfsite.iconsole.R;
import org.surfsite.iconsole.common.logger.Log;
import java.util.Locale;
/** /**
* This fragment controls Bluetooth to communicate with other devices. * This fragment controls Bluetooth to communicate with other devices.
@ -66,6 +62,13 @@ public class BluetoothChatFragment extends Fragment {
private Button mStopButton; private Button mStopButton;
private Button mDisconnectButton; private Button mDisconnectButton;
private NumberPicker mLevel; private NumberPicker mLevel;
private TextView mSpeedText;
private TextView mPowerText;
private TextView mRPMText;
private TextView mDistanceText;
private TextView mCaloriesText;
private TextView mHFText;
private TextView mTimeText;
/** /**
* Name of the connected device * Name of the connected device
*/ */
@ -85,6 +88,7 @@ public class BluetoothChatFragment extends Fragment {
* Member object for the chat services * Member object for the chat services
*/ */
private BluetoothChatService mChatService = null; private BluetoothChatService mChatService = null;
private boolean mIsBound;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -118,10 +122,12 @@ public class BluetoothChatFragment extends Fragment {
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy();
if (mChatService != null) { if (mChatService != null) {
mChatService.stop(); mChatService.stopBT();
} }
Log.d(TAG, "onDestroy()");
super.onDestroy();
} }
@Override @Override
@ -135,7 +141,7 @@ public class BluetoothChatFragment extends Fragment {
// Only if the state is STATE_NONE, do we know that we haven't started already // Only if the state is STATE_NONE, do we know that we haven't started already
if (mChatService.getState() == BluetoothChatService.STATE_NONE) { if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
// Start the Bluetooth chat services // Start the Bluetooth chat services
mChatService.start(); mChatService.startBT();
} }
} }
} }
@ -158,6 +164,54 @@ public class BluetoothChatFragment extends Fragment {
mLevel.setValue(5); mLevel.setValue(5);
mLevel.setWrapSelectorWheel(false); mLevel.setWrapSelectorWheel(false);
mLevel.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); mLevel.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
mSpeedText = (TextView) view.findViewById(R.id.Speed);
mPowerText = (TextView) view.findViewById(R.id.Power);
mRPMText = (TextView) view.findViewById(R.id.RPM);
mDistanceText = (TextView) view.findViewById(R.id.Distance);
mCaloriesText = (TextView) view.findViewById(R.id.Calories);
mHFText = (TextView) view.findViewById(R.id.Heart);
mTimeText = (TextView) view.findViewById(R.id.Time);
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mChatService = ((BluetoothChatService.BluetoothChatServiceI)service).getService();
((BluetoothChatService.BluetoothChatServiceI)service).setHandler(mHandler);
Log.d(TAG, "onServiceConnected()");
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mChatService = null;
}
};
void doBindService() {
Log.d(TAG, "doBindService()");
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
getActivity().bindService(new Intent(getActivity(), BluetoothChatService.class), mConnection , Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
getActivity().unbindService(mConnection);
mIsBound = false;
}
} }
/** /**
@ -165,37 +219,45 @@ public class BluetoothChatFragment extends Fragment {
*/ */
private void setupChat() { private void setupChat() {
Log.d(TAG, "setupChat()"); Log.d(TAG, "setupChat()");
/*
// Initialize the array adapter for the conversation thread
mConversationArrayAdapter = new ArrayAdapter<>(getActivity(), R.layout.message);
mConversationView.setAdapter(mConversationArrayAdapter); if (!mIsBound)
*/ doBindService();
// Initialize the BluetoothChatService to perform bluetooth connections
mChatService = new BluetoothChatService(getActivity(), mHandler);
mStartButton.setOnClickListener(new View.OnClickListener() { mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { public void onClick(View v) {
if (mChatService != null)
mChatService.startIConsole(); mChatService.startIConsole();
} }
}); });
mStopButton.setOnClickListener(new View.OnClickListener() { mStopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { public void onClick(View v) {
if (mChatService != null)
mChatService.stopIConsole(); mChatService.stopIConsole();
} }
}); });
mDisconnectButton.setOnClickListener(new View.OnClickListener() { mDisconnectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { public void onClick(View v) {
mChatService.stop(); if (mChatService != null)
mChatService.stopBT();
} }
}); });
mStartButton.setEnabled(false); mStartButton.setEnabled(false);
mStopButton.setEnabled(false); mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false); mDisconnectButton.setEnabled(false);
mLevel.setEnabled(false);
mLevel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mChatService != null)
mChatService.setLevel(mLevel.getValue());
}
});
} }
/** /**
@ -216,6 +278,9 @@ public class BluetoothChatFragment extends Fragment {
* @param message A string of text to send. * @param message A string of text to send.
*/ */
private void sendMessage(String message) { private void sendMessage(String message) {
if (mChatService == null)
return;
// Check that we're actually connected before trying anything // Check that we're actually connected before trying anything
if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) { if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
Toast.makeText(getActivity(), R.string.not_connected, Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), R.string.not_connected, Toast.LENGTH_SHORT).show();
@ -263,6 +328,7 @@ public class BluetoothChatFragment extends Fragment {
* The Handler that gets information back from the BluetoothChatService * The Handler that gets information back from the BluetoothChatService
*/ */
private final Handler mHandler = new Handler() { private final Handler mHandler = new Handler() {
@SuppressLint("DefaultLocale")
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
FragmentActivity activity = getActivity(); FragmentActivity activity = getActivity();
@ -275,12 +341,14 @@ public class BluetoothChatFragment extends Fragment {
mStartButton.setEnabled(true); mStartButton.setEnabled(true);
mStopButton.setEnabled(true); mStopButton.setEnabled(true);
mDisconnectButton.setEnabled(true); mDisconnectButton.setEnabled(true);
mLevel.setEnabled(true);
break; break;
case BluetoothChatService.STATE_CONNECTING: case BluetoothChatService.STATE_CONNECTING:
setStatus(R.string.title_connecting); setStatus(R.string.title_connecting);
mStartButton.setEnabled(false); mStartButton.setEnabled(false);
mStopButton.setEnabled(false); mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false); mDisconnectButton.setEnabled(false);
mLevel.setEnabled(false);
break; break;
case BluetoothChatService.STATE_LISTEN: case BluetoothChatService.STATE_LISTEN:
case BluetoothChatService.STATE_NONE: case BluetoothChatService.STATE_NONE:
@ -288,19 +356,22 @@ public class BluetoothChatFragment extends Fragment {
mStartButton.setEnabled(false); mStartButton.setEnabled(false);
mStopButton.setEnabled(false); mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false); mDisconnectButton.setEnabled(false);
mLevel.setEnabled(false);
break; break;
} }
break; break;
case Constants.MESSAGE_DATA: case Constants.MESSAGE_DATA:
if (!(msg.obj instanceof IConsole.Data))
return;
IConsole.Data data = (IConsole.Data) msg.obj; IConsole.Data data = (IConsole.Data) msg.obj;
// FIXME mSpeedText.setText(String.format("Speed\n% 3.1f", data.mSpeed10 / 10.0));
// insert text here mPowerText.setText(String.format("Power\n% 3.1f", data.mPower10 / 10.0));
/* mRPMText.setText(String.format("RPM\n%d", data.mRPM));
String dataMessage = String.format(Locale.US, "Time: %d Speed: %3.1f Power: %3.1f RPM: %d LVL: %d Dist: %4.1f Cal: %d HF: %d", mDistanceText.setText(String.format("Distance\n% 3.1f", data.mDistance10 / 10.0));
data.mTime, data.mSpeed10 / 10.0, data.mPower10 / 10.0, data.mRPM, mCaloriesText.setText(String.format("Calories\n% 3.1f", data.mSpeed10 / 10.0));
data.mLevel, data.mDistance10 / 10.0, data.mCalories, data.mHF); mHFText.setText(String.format("Heart\n%d", data.mHF));
*/ mTimeText.setText(String.format("Time:\n%s",data.getTimeStr()));
//mConversationArrayAdapter.add(mConnectedDeviceName + ": " + dataMessage); mLevel.setValue(data.mLevel);
break; break;
case Constants.MESSAGE_WRITE: case Constants.MESSAGE_WRITE:
//byte[] writeBuf = (byte[]) msg.obj; //byte[] writeBuf = (byte[]) msg.obj;
@ -374,6 +445,7 @@ public class BluetoothChatFragment extends Fragment {
// Get the BluetoothDevice object // Get the BluetoothDevice object
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
// Attempt to connect to the device // Attempt to connect to the device
if (mChatService != null)
mChatService.connect(device); mChatService.connect(device);
} }

View file

@ -16,20 +16,26 @@
package org.surfsite.iconsole; package org.surfsite.iconsole;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder;
import android.os.Message; import android.os.Message;
import org.surfsite.iconsole.common.logger.Log; import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
/** /**
@ -38,7 +44,7 @@ import java.util.UUID;
* incoming connections, a thread for connecting with a device, and a * incoming connections, a thread for connecting with a device, and a
* thread for performing data transmissions when connected. * thread for performing data transmissions when connected.
*/ */
public class BluetoothChatService { public class BluetoothChatService extends Service {
// Debugging // Debugging
private static final String TAG = "BluetoothChatService"; private static final String TAG = "BluetoothChatService";
// Name for the SDP record when creating server socket // Name for the SDP record when creating server socket
@ -49,7 +55,7 @@ public class BluetoothChatService {
private static final UUID SERIAL_PORT_CLASS = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); private static final UUID SERIAL_PORT_CLASS = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
// Member fields // Member fields
private final BluetoothAdapter mAdapter; private final BluetoothAdapter mAdapter;
private final Handler mHandler; private Handler mHandler;
private ConnectThread mConnectThread; private ConnectThread mConnectThread;
private ConnectedThread mConnectedThread; private ConnectedThread mConnectedThread;
@ -61,60 +67,47 @@ public class BluetoothChatService {
public static final int STATE_LISTEN = 1; // now listening for incoming connections public static final int STATE_LISTEN = 1; // now listening for incoming connections
public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 3; // now connected to a remote device public static final int STATE_CONNECTED = 3; // now connected to a remote device
private NotificationManager mNM;
/** private int NOTIFICATION = R.string.local_service_started;
* Constructor. Prepares a new BluetoothChat session.
* public BluetoothChatService() {
* @param context The UI Activity Context super();
* @param handler A Handler to send messages back to the UI Activity
*/
public BluetoothChatService(Context context, Handler handler) {
mAdapter = BluetoothAdapter.getDefaultAdapter(); mAdapter = BluetoothAdapter.getDefaultAdapter();
mState = STATE_NONE; mState = STATE_NONE;
mNewState = mState; mNewState = mState;
}
public class BluetoothChatServiceI extends Binder {
BluetoothChatService getService() {
return BluetoothChatService.this;
}
void setHandler(Handler handler) {
mHandler = handler; mHandler = handler;
} }
/**
* Update UI title according to the current state of the chat connection
*/
private synchronized void updateUserInterfaceTitle() {
mState = getState();
Log.d(TAG, "updateUserInterfaceTitle() " + mNewState + " -> " + mState);
mNewState = mState;
// Give the new state to the Handler so the UI Activity can update
mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget();
} }
/** @Override
* Return the current connection state. public void onCreate() {
*/ super.onCreate();
public synchronized int getState() {
return mState; mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
} }
/** boolean startIConsole() {
* Start the chat service. Specifically start AcceptThread to begin a return mConnectedThread.startIConsole();
* session in listening (server) mode. Called by the Activity onResume()
*/
public synchronized void start() {
Log.d(TAG, "start");
// Cancel any thread attempting to make a connection
if (mConnectThread != null) {
mConnectThread.cancel();
mConnectThread = null;
} }
// Cancel any thread currently running a connection boolean stopIConsole() {
if (mConnectedThread != null) { return mConnectedThread.stopIConsole();
mConnectedThread.cancel();
mConnectedThread = null;
} }
// Update UI title
updateUserInterfaceTitle(); boolean setLevel(int level) {
return mConnectedThread.setLevel(level);
} }
/** /**
@ -146,6 +139,100 @@ public class BluetoothChatService {
updateUserInterfaceTitle(); updateUserInterfaceTitle();
} }
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind");
return mBinder;
}
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new BluetoothChatServiceI();
/**
* Update UI title according to the current state of the chat connection
*/
private synchronized void updateUserInterfaceTitle() {
mState = getState();
Log.d(TAG, "updateUserInterfaceTitle() " + mNewState + " -> " + mState);
mNewState = mState;
// Give the new state to the Handler so the UI Activity can update
mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget();
}
/**
* Return the current connection state.
*/
public synchronized int getState() {
return mState;
}
/**
* Start the chat service. Specifically startBT AcceptThread to begin a
* session in listening (server) mode. Called by the Activity onResume()
*/
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Received onStartCommand() id " + startId + ": " + intent);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
stopBT();
// Cancel the persistent notification.
mNM.cancel(NOTIFICATION);
// Tell the user we stopped.
Log.i(TAG, "onDestroy");
super.onDestroy();
}
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.local_service_started);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_action_device_access_bluetooth_searching) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.local_service_label)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
mNM.notify(NOTIFICATION, notification);
}
void startBT() {
Log.d(TAG, "startBT");
// Cancel any thread attempting to make a connection
if (mConnectThread != null) {
mConnectThread.cancel();
mConnectThread = null;
}
// Cancel any thread currently running a connection
if (mConnectedThread != null) {
mConnectedThread.cancel();
mConnectedThread = null;
}
// Update UI title
updateUserInterfaceTitle();
}
/** /**
* Start the ConnectedThread to begin managing a Bluetooth connection * Start the ConnectedThread to begin managing a Bluetooth connection
* *
@ -185,8 +272,8 @@ public class BluetoothChatService {
/** /**
* Stop all threads * Stop all threads
*/ */
public synchronized void stop() { public synchronized void stopBT() {
Log.d(TAG, "stop"); Log.d(TAG, "stopBT");
if (mConnectThread != null) { if (mConnectThread != null) {
mConnectThread.cancel(); mConnectThread.cancel();
@ -203,13 +290,6 @@ public class BluetoothChatService {
updateUserInterfaceTitle(); updateUserInterfaceTitle();
} }
public synchronized boolean startIConsole() {
return mConnectedThread.startIConsole();
}
public synchronized boolean stopIConsole() {
return mConnectedThread.stopIConsole();
}
/** /**
* Indicate that the connection attempt failed and notify the UI Activity. * Indicate that the connection attempt failed and notify the UI Activity.
@ -227,7 +307,7 @@ public class BluetoothChatService {
updateUserInterfaceTitle(); updateUserInterfaceTitle();
// Start the service over to restart listening mode // Start the service over to restart listening mode
BluetoothChatService.this.start(); BluetoothChatService.this.startBT();
} }
/** /**
@ -246,7 +326,7 @@ public class BluetoothChatService {
updateUserInterfaceTitle(); updateUserInterfaceTitle();
// Start the service over to restart listening mode // Start the service over to restart listening mode
BluetoothChatService.this.start(); BluetoothChatService.this.startBT();
} }
/** /**
@ -254,7 +334,7 @@ public class BluetoothChatService {
* with a device. It runs straight through; the connection either * with a device. It runs straight through; the connection either
* succeeds or fails. * succeeds or fails.
*/ */
private class ConnectThread extends Thread { class ConnectThread extends Thread {
private final BluetoothSocket mmSocket; private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice; private final BluetoothDevice mmDevice;
@ -318,7 +398,7 @@ public class BluetoothChatService {
* This thread runs during a connection with a remote device. * This thread runs during a connection with a remote device.
* It handles all incoming and outgoing transmissions. * It handles all incoming and outgoing transmissions.
*/ */
private class ConnectedThread extends Thread { class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket; private final BluetoothSocket mmSocket;
private final InputStream mmInStream; private final InputStream mmInStream;
private final OutputStream mmOutStream; private final OutputStream mmOutStream;

View file

@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.widget.AdapterView; import android.widget.AdapterView;
@ -33,7 +34,6 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import org.surfsite.iconsole.R; import org.surfsite.iconsole.R;
import org.surfsite.iconsole.common.logger.Log;
import java.util.Set; import java.util.Set;
@ -153,7 +153,7 @@ public class DeviceListActivity extends Activity {
// Turn on sub-title for new devices // Turn on sub-title for new devices
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE); findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
// If we're already discovering, stop it // If we're already discovering, stopBT it
if (mBtAdapter.isDiscovering()) { if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery(); mBtAdapter.cancelDiscovery();
} }

View file

@ -1,12 +1,14 @@
package org.surfsite.iconsole; package org.surfsite.iconsole;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.surfsite.iconsole.common.logger.Log;
/** /**
* Created by harald on 25.04.17. * Created by harald on 25.04.17.
@ -52,7 +54,8 @@ class IConsole {
private final DataListener mDataListener; private final DataListener mDataListener;
private final DebugListener mDebugListener; private final DebugListener mDebugListener;
IConsole(InputStream inputStream, OutputStream outputStream, DataListener dataListener, DebugListener debugListener) { IConsole(InputStream inputStream, OutputStream outputStream,
@Nullable DataListener dataListener, @Nullable DebugListener debugListener) {
this.mInputStream = inputStream; this.mInputStream = inputStream;
this.mOutputStream = outputStream; this.mOutputStream = outputStream;
this.mDataListener = dataListener; this.mDataListener = dataListener;
@ -90,6 +93,25 @@ class IConsole {
this.mPower10 = 100 * (bytes[16] - 1) + bytes[17] - 1; this.mPower10 = 100 * (bytes[16] - 1) + bytes[17] - 1;
this.mLevel = bytes[18] -1; this.mLevel = bytes[18] -1;
} }
String getTimeStr() {
long day, hour, min, sec;
StringBuilder b = new StringBuilder();
day = mTime / 60 / 60 / 24;
if (day > 0)
b.append(String.format(Locale.US, "%02d:", day));
hour = (mTime % (60 * 60 * 24)) / 60 / 60;
if (hour > 0)
if (day > 0)
b.append(String.format(Locale.US, "%02d:", hour));
else
b.append(String.format(Locale.US, "%d:", hour));
min = (mTime % (60 * 60)) / 60;
sec = mTime % 60;
b.append(String.format(Locale.US, "%02d:%02d", min, sec));
return b.toString();
}
} }
interface DataListener { interface DataListener {
@ -127,7 +149,7 @@ class IConsole {
return true; return true;
} }
boolean send(byte[] packet, byte expect, int plen) throws IOException { private boolean send(byte[] packet, byte expect, int plen) throws IOException {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if ((now - mTimesent) < ((mCurrentState == State.READ) ? 500 : 200)) { if ((now - mTimesent) < ((mCurrentState == State.READ) ? 500 : 200)) {
@ -136,6 +158,7 @@ class IConsole {
// Flush input stream // Flush input stream
try { try {
//noinspection ResultOfMethodCallIgnored
mInputStream.skip(mInputStream.available()); mInputStream.skip(mInputStream.available());
} catch (IOException e) { } catch (IOException e) {
; // ignore ; // ignore
@ -263,6 +286,7 @@ class IConsole {
//Log.d(TAG, "processIOAck next state"); //Log.d(TAG, "processIOAck next state");
if(mCurrentState == State.READ) if(mCurrentState == State.READ)
if (null != mDataListener)
mDataListener.onData(new Data(got)); mDataListener.onData(new Data(got));
mCurrentState = mNextState; mCurrentState = mNextState;
@ -318,6 +342,7 @@ class IConsole {
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "processIO", e); Log.e(TAG, "processIO", e);
if (null != mDataListener)
mDataListener.onError(e); mDataListener.onError(e);
return false; return false;
} }

View file

@ -17,18 +17,22 @@
package org.surfsite.iconsole; package org.surfsite.iconsole;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.WindowManager;
import android.widget.ViewAnimator; import android.widget.ViewAnimator;
import org.surfsite.iconsole.R;
import org.surfsite.iconsole.common.activities.SampleActivityBase;
import org.surfsite.iconsole.common.logger.Log;
import org.surfsite.iconsole.common.logger.LogFragment;
import org.surfsite.iconsole.common.logger.LogWrapper;
import org.surfsite.iconsole.common.logger.MessageOnlyLogFilter;
/** /**
* A simple launcher activity containing a summary sample description, sample log and a custom * A simple launcher activity containing a summary sample description, sample log and a custom
@ -37,7 +41,7 @@ import org.surfsite.iconsole.common.logger.MessageOnlyLogFilter;
* For devices with displays with a width of 720dp or greater, the sample log is always visible, * For devices with displays with a width of 720dp or greater, the sample log is always visible,
* on other devices it's visibility is controlled by an item on the Action Bar. * on other devices it's visibility is controlled by an item on the Action Bar.
*/ */
public class MainActivity extends SampleActivityBase { public class MainActivity extends FragmentActivity {
public static final String TAG = "MainActivity"; public static final String TAG = "MainActivity";
@ -55,6 +59,12 @@ public class MainActivity extends SampleActivityBase {
transaction.replace(R.id.sample_content_fragment, fragment); transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit(); transaction.commit();
} }
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
ComponentName componentName = startService(new Intent(this, BluetoothChatService.class));
if (componentName == null)
Log.e(TAG, "componentName == null");
else
Log.i(TAG, "Service started");
} }
@Override @Override
@ -66,7 +76,7 @@ public class MainActivity extends SampleActivityBase {
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem logToggle = menu.findItem(R.id.menu_toggle_log); MenuItem logToggle = menu.findItem(R.id.menu_toggle_log);
logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator); //logToggle.setVisible(findViewById(R.id.sample_output) instanceof ViewAnimator);
logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log); logToggle.setTitle(mLogShown ? R.string.sample_hide_log : R.string.sample_show_log);
return super.onPrepareOptionsMenu(menu); return super.onPrepareOptionsMenu(menu);
@ -90,9 +100,7 @@ public class MainActivity extends SampleActivityBase {
*/ */
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
/*
/** Create a chain of targets that will receive log data */
@Override
public void initializeLogging() { public void initializeLogging() {
// Wraps Android's native log framework. // Wraps Android's native log framework.
LogWrapper logWrapper = new LogWrapper(); LogWrapper logWrapper = new LogWrapper();
@ -110,4 +118,5 @@ public class MainActivity extends SampleActivityBase {
Log.i(TAG, "Ready"); Log.i(TAG, "Ready");
} }
*/
} }

View file

@ -1,52 +0,0 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.surfsite.iconsole.common.activities;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import org.surfsite.iconsole.common.logger.Log;
import org.surfsite.iconsole.common.logger.LogWrapper;
/**
* Base launcher activity, to handle most of the common plumbing for samples.
*/
public class SampleActivityBase extends FragmentActivity {
public static final String TAG = "SampleActivityBase";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
protected void onStart() {
super.onStart();
initializeLogging();
}
/** Set up targets to receive log data */
public void initializeLogging() {
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
// Wraps Android's native log framework
LogWrapper logWrapper = new LogWrapper();
Log.setLogNode(logWrapper);
Log.i(TAG, "Ready");
}
}

View file

@ -1,236 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.surfsite.iconsole.common.logger;
/**
* Helper class for a list (or tree) of LoggerNodes.
*
* <p>When this is set as the head of the list,
* an instance of it can function as a drop-in replacement for {@link android.util.Log}.
* Most of the methods in this class server only to map a method call in Log to its equivalent
* in LogNode.</p>
*/
public class Log {
// Grabbing the native values from Android's native logging facilities,
// to make for easy migration and interop.
public static final int NONE = -1;
public static final int VERBOSE = android.util.Log.VERBOSE;
public static final int DEBUG = android.util.Log.DEBUG;
public static final int INFO = android.util.Log.INFO;
public static final int WARN = android.util.Log.WARN;
public static final int ERROR = android.util.Log.ERROR;
public static final int ASSERT = android.util.Log.ASSERT;
// Stores the beginning of the LogNode topology.
private static LogNode mLogNode;
/**
* Returns the next LogNode in the linked list.
*/
public static LogNode getLogNode() {
return mLogNode;
}
/**
* Sets the LogNode data will be sent to.
*/
public static void setLogNode(LogNode node) {
mLogNode = node;
}
/**
* Instructs the LogNode to print the log data provided. Other LogNodes can
* be chained to the end of the LogNode as desired.
*
* @param priority Log level of the data being logged. Verbose, Error, etc.
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void println(int priority, String tag, String msg, Throwable tr) {
if (mLogNode != null) {
mLogNode.println(priority, tag, msg, tr);
}
}
/**
* Instructs the LogNode to print the log data provided. Other LogNodes can
* be chained to the end of the LogNode as desired.
*
* @param priority Log level of the data being logged. Verbose, Error, etc.
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged. The actual message to be logged.
*/
public static void println(int priority, String tag, String msg) {
println(priority, tag, msg, null);
}
/**
* Prints a message at VERBOSE priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void v(String tag, String msg, Throwable tr) {
println(VERBOSE, tag, msg, tr);
}
/**
* Prints a message at VERBOSE priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
*/
public static void v(String tag, String msg) {
v(tag, msg, null);
}
/**
* Prints a message at DEBUG priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void d(String tag, String msg, Throwable tr) {
println(DEBUG, tag, msg, tr);
}
/**
* Prints a message at DEBUG priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
*/
public static void d(String tag, String msg) {
d(tag, msg, null);
}
/**
* Prints a message at INFO priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void i(String tag, String msg, Throwable tr) {
println(INFO, tag, msg, tr);
}
/**
* Prints a message at INFO priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
*/
public static void i(String tag, String msg) {
i(tag, msg, null);
}
/**
* Prints a message at WARN priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void w(String tag, String msg, Throwable tr) {
println(WARN, tag, msg, tr);
}
/**
* Prints a message at WARN priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
*/
public static void w(String tag, String msg) {
w(tag, msg, null);
}
/**
* Prints a message at WARN priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void w(String tag, Throwable tr) {
w(tag, null, tr);
}
/**
* Prints a message at ERROR priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void e(String tag, String msg, Throwable tr) {
println(ERROR, tag, msg, tr);
}
/**
* Prints a message at ERROR priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
*/
public static void e(String tag, String msg) {
e(tag, msg, null);
}
/**
* Prints a message at ASSERT priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void wtf(String tag, String msg, Throwable tr) {
println(ASSERT, tag, msg, tr);
}
/**
* Prints a message at ASSERT priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged.
*/
public static void wtf(String tag, String msg) {
wtf(tag, msg, null);
}
/**
* Prints a message at ASSERT priority.
*
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public static void wtf(String tag, Throwable tr) {
wtf(tag, null, tr);
}
}

View file

@ -1,109 +0,0 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.surfsite.iconsole.common.logger;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
/**
* Simple fraggment which contains a LogView and uses is to output log data it receives
* through the LogNode interface.
*/
public class LogFragment extends Fragment {
private LogView mLogView;
private ScrollView mScrollView;
public LogFragment() {}
public View inflateViews() {
mScrollView = new ScrollView(getActivity());
ViewGroup.LayoutParams scrollParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
mScrollView.setLayoutParams(scrollParams);
mLogView = new LogView(getActivity());
ViewGroup.LayoutParams logParams = new ViewGroup.LayoutParams(scrollParams);
logParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
mLogView.setLayoutParams(logParams);
mLogView.setClickable(true);
mLogView.setFocusable(true);
mLogView.setTypeface(Typeface.MONOSPACE);
// Want to set padding as 16 dips, setPadding takes pixels. Hooray math!
int paddingDips = 16;
double scale = getResources().getDisplayMetrics().density;
int paddingPixels = (int) ((paddingDips * (scale)) + .5);
mLogView.setPadding(paddingPixels, paddingPixels, paddingPixels, paddingPixels);
mLogView.setCompoundDrawablePadding(paddingPixels);
mLogView.setGravity(Gravity.BOTTOM);
mLogView.setTextAppearance(getActivity(), android.R.style.TextAppearance_Holo_Medium);
mScrollView.addView(mLogView);
return mScrollView;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View result = inflateViews();
mLogView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
return result;
}
public LogView getLogView() {
return mLogView;
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.surfsite.iconsole.common.logger;
/**
* Basic interface for a logging system that can output to one or more targets.
* Note that in addition to classes that will output these logs in some format,
* one can also implement this interface over a filter and insert that in the chain,
* such that no targets further down see certain data, or see manipulated forms of the data.
* You could, for instance, write a "ToHtmlLoggerNode" that just converted all the log data
* it received to HTML and sent it along to the next node in the chain, without printing it
* anywhere.
*/
public interface LogNode {
/**
* Instructs first LogNode in the list to print the log data provided.
* @param priority Log level of the data being logged. Verbose, Error, etc.
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged. The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
public void println(int priority, String tag, String msg, Throwable tr);
}

View file

@ -1,145 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.surfsite.iconsole.common.logger;
import android.app.Activity;
import android.content.Context;
import android.util.*;
import android.widget.TextView;
/** Simple TextView which is used to output log data received through the LogNode interface.
*/
public class LogView extends TextView implements LogNode {
public LogView(Context context) {
super(context);
}
public LogView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LogView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Formats the log data and prints it out to the LogView.
* @param priority Log level of the data being logged. Verbose, Error, etc.
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged. The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
@Override
public void println(int priority, String tag, String msg, Throwable tr) {
String priorityStr = null;
// For the purposes of this View, we want to print the priority as readable text.
switch(priority) {
case android.util.Log.VERBOSE:
priorityStr = "VERBOSE";
break;
case android.util.Log.DEBUG:
priorityStr = "DEBUG";
break;
case android.util.Log.INFO:
priorityStr = "INFO";
break;
case android.util.Log.WARN:
priorityStr = "WARN";
break;
case android.util.Log.ERROR:
priorityStr = "ERROR";
break;
case android.util.Log.ASSERT:
priorityStr = "ASSERT";
break;
default:
break;
}
// Handily, the Log class has a facility for converting a stack trace into a usable string.
String exceptionStr = null;
if (tr != null) {
exceptionStr = android.util.Log.getStackTraceString(tr);
}
// Take the priority, tag, message, and exception, and concatenate as necessary
// into one usable line of text.
final StringBuilder outputBuilder = new StringBuilder();
String delimiter = "\t";
appendIfNotNull(outputBuilder, priorityStr, delimiter);
appendIfNotNull(outputBuilder, tag, delimiter);
appendIfNotNull(outputBuilder, msg, delimiter);
appendIfNotNull(outputBuilder, exceptionStr, delimiter);
// In case this was originally called from an AsyncTask or some other off-UI thread,
// make sure the update occurs within the UI thread.
((Activity) getContext()).runOnUiThread( (new Thread(new Runnable() {
@Override
public void run() {
// Display the text we just generated within the LogView.
appendToLog(outputBuilder.toString());
}
})));
if (mNext != null) {
mNext.println(priority, tag, msg, tr);
}
}
public LogNode getNext() {
return mNext;
}
public void setNext(LogNode node) {
mNext = node;
}
/** Takes a string and adds to it, with a separator, if the bit to be added isn't null. Since
* the logger takes so many arguments that might be null, this method helps cut out some of the
* agonizing tedium of writing the same 3 lines over and over.
* @param source StringBuilder containing the text to append to.
* @param addStr The String to append
* @param delimiter The String to separate the source and appended strings. A tab or comma,
* for instance.
* @return The fully concatenated String as a StringBuilder
*/
private StringBuilder appendIfNotNull(StringBuilder source, String addStr, String delimiter) {
if (addStr != null) {
if (addStr.length() == 0) {
delimiter = "";
}
return source.append(addStr).append(delimiter);
}
return source;
}
// The next LogNode in the chain.
LogNode mNext;
/** Outputs the string as a new line of log data in the LogView. */
public void appendToLog(String s) {
append("\n" + s);
}
}

View file

@ -1,75 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.surfsite.iconsole.common.logger;
import android.util.Log;
/**
* Helper class which wraps Android's native Log utility in the Logger interface. This way
* normal DDMS output can be one of the many targets receiving and outputting logs simultaneously.
*/
public class LogWrapper implements LogNode {
// For piping: The next node to receive Log data after this one has done its work.
private LogNode mNext;
/**
* Returns the next LogNode in the linked list.
*/
public LogNode getNext() {
return mNext;
}
/**
* Sets the LogNode data will be sent to..
*/
public void setNext(LogNode node) {
mNext = node;
}
/**
* Prints data out to the console using Android's native log mechanism.
* @param priority Log level of the data being logged. Verbose, Error, etc.
* @param tag Tag for for the log data. Can be used to organize log statements.
* @param msg The actual message to be logged. The actual message to be logged.
* @param tr If an exception was thrown, this can be sent along for the logging facilities
* to extract and print useful information.
*/
@Override
public void println(int priority, String tag, String msg, Throwable tr) {
// There actually are log methods that don't take a msg parameter. For now,
// if that's the case, just convert null to the empty string and move on.
String useMsg = msg;
if (useMsg == null) {
useMsg = "";
}
// If an exeption was provided, convert that exception to a usable string and attach
// it to the end of the msg method.
if (tr != null) {
msg += "\n" + Log.getStackTraceString(tr);
}
// This is functionally identical to Log.x(tag, useMsg);
// For instance, if priority were Log.VERBOSE, this would be the same as Log.v(tag, useMsg)
Log.println(priority, tag, useMsg);
// If this isn't the last node in the chain, move things along.
if (mNext != null) {
mNext.println(priority, tag, msg, tr);
}
}
}

View file

@ -1,60 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.surfsite.iconsole.common.logger;
/**
* Simple {@link LogNode} filter, removes everything except the message.
* Useful for situations like on-screen log output where you don't want a lot of metadata displayed,
* just easy-to-read message updates as they're happening.
*/
public class MessageOnlyLogFilter implements LogNode {
LogNode mNext;
/**
* Takes the "next" LogNode as a parameter, to simplify chaining.
*
* @param next The next LogNode in the pipeline.
*/
public MessageOnlyLogFilter(LogNode next) {
mNext = next;
}
public MessageOnlyLogFilter() {
}
@Override
public void println(int priority, String tag, String msg, Throwable tr) {
if (mNext != null) {
getNext().println(Log.NONE, null, msg, null);
}
}
/**
* Returns the next LogNode in the chain.
*/
public LogNode getNext() {
return mNext;
}
/**
* Sets the LogNode data will be sent to..
*/
public void setNext(LogNode node) {
mNext = node;
}
}

View file

@ -20,48 +20,6 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/sample_main_layout"> android:id="@+id/sample_main_layout">
<LinearLayout
android:id="@+id/sample_output"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<FrameLayout
style="@style/Widget.SampleMessageTile"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
style="@style/Widget.SampleMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/margin_medium"
android:paddingRight="@dimen/margin_medium"
android:paddingTop="@dimen/margin_large"
android:paddingBottom="@dimen/margin_large"
android:text="@string/intro_message" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray" />
<fragment
android:name="org.surfsite.iconsole.common.logger.LogFragment"
android:id="@+id/log_fragment"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1" />
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@android:color/darker_gray" />
<FrameLayout <FrameLayout
android:id="@+id/sample_content_fragment" android:id="@+id/sample_content_fragment"
android:layout_weight="2" android:layout_weight="2"

View file

@ -20,28 +20,9 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/sample_main_layout"> android:id="@+id/sample_main_layout">
<FrameLayout
android:id="@+id/sample_output"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1">
<fragment
android:name="org.surfsite.iconsole.common.logger.LogFragment"
android:id="@+id/log_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray" />
<FrameLayout <FrameLayout
android:id="@+id/sample_content_fragment" android:id="@+id/sample_content_fragment"
android:layout_weight="2" android:layout_weight="4"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0px" /> android:layout_height="0px" />

View file

@ -22,147 +22,272 @@
tools:layout_editor_absoluteY="0dp" tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="0dp" > tools:layout_editor_absoluteX="0dp" >
<TextView
android:id="@+id/textView8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RPM"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/RPM"
app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline6"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Calories"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/Calories"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
android:layout_marginRight="16dp"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline6"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Heart"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/Heart"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp"
app:layout_constraintTop_toTopOf="@+id/guideline4"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Distance"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/Distance"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp"
app:layout_constraintTop_toTopOf="@+id/guideline5"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Speed"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/Speed"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
android:layout_marginRight="16dp"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline4"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:text="Power"
app:layout_constraintBottom_toTopOf="@+id/Power"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline5"
app:layout_constraintVertical_bias="0.333" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:text="Time"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
app:layout_constraintBottom_toTopOf="@+id/Time"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<NumberPicker
android:id="@+id/Level"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintLeft_toLeftOf="@+id/guideline"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline4"
app:layout_constraintHorizontal_bias="0.484"
app:layout_constraintVertical_bias="0.671" />
<TextView <TextView
android:id="@+id/Time" android:id="@+id/Time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center" android:gravity="center"
android:text="@string/time_n00_00_00" android:text="@string/time_n00_00_00"
android:textAlignment="center" android:textAlignment="center"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline5" app:layout_constraintBottom_toTopOf="@+id/guideline5"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.513"
android:layout_marginRight="16dp"
app:layout_constraintRight_toRightOf="parent" />
<NumberPicker
android:id="@+id/Level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
android:layout_marginBottom="16dp" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/guideline4"
android:layout_marginTop="16dp" />
<TextView <TextView
android:id="@+id/Speed" android:id="@+id/Speed"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center" android:gravity="center"
android:text="@string/speed_n0_0_km_h" android:text="@string/speed_n0_0_km_h"
android:textAlignment="center" android:textAlignment="center"
app:layout_constraintTop_toTopOf="@+id/guideline4" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:layout_marginBottom="16dp" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintBottom_toTopOf="@+id/guideline6"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/guideline3" app:layout_constraintRight_toLeftOf="@+id/guideline3"
android:layout_marginRight="16dp" /> app:layout_constraintTop_toTopOf="@+id/guideline4" />
<TextView <TextView
android:id="@+id/Power" android:id="@+id/Power"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center"
android:text="@string/power_n0_0_w" android:text="@string/power_n0_0_w"
android:textAlignment="center" android:textAlignment="center"
android:gravity="center" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
app:layout_constraintRight_toLeftOf="@+id/guideline3" android:textStyle="bold"
app:layout_constraintTop_toTopOf="@+id/guideline5" app:layout_constraintBottom_toTopOf="@+id/guideline4"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginBottom="16dp" app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintBottom_toTopOf="@+id/guideline4" /> app:layout_constraintTop_toTopOf="@+id/guideline5" />
<TextView <TextView
android:id="@+id/RPM" android:id="@+id/RPM"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center"
android:text="@string/rpm_n0" android:text="@string/rpm_n0"
android:textAlignment="center" android:textAlignment="center"
android:gravity="center" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
app:layout_constraintTop_toTopOf="@+id/guideline6" android:textStyle="bold"
android:layout_marginTop="16dp" app:layout_constraintBottom_toTopOf="@+id/guideline2"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline3" app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintRight_toLeftOf="@+id/guideline" app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp" app:layout_constraintTop_toTopOf="@+id/guideline6" />
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2" />
<TextView <TextView
android:id="@+id/Distance" android:id="@+id/Distance"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center"
android:text="@string/distance_n0_0_km" android:text="@string/distance_n0_0_km"
android:textAlignment="center" android:textAlignment="center"
android:gravity="center" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
app:layout_constraintTop_toTopOf="@+id/guideline5" android:textStyle="bold"
android:layout_marginTop="16dp"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintVertical_bias="0.538" app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintRight_toLeftOf="@+id/guideline" app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp" /> app:layout_constraintTop_toTopOf="@+id/guideline5"
app:layout_constraintVertical_bias="0.538" />
<TextView <TextView
android:id="@+id/Calories" android:id="@+id/Calories"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center"
android:text="@string/calories_n0_cal" android:text="@string/calories_n0_cal"
android:textAlignment="center" android:textAlignment="center"
android:gravity="center" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
app:layout_constraintRight_toLeftOf="@+id/guideline3" android:textStyle="bold"
app:layout_constraintTop_toTopOf="@+id/guideline6" app:layout_constraintBottom_toTopOf="@+id/guideline2"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="0.507" app:layout_constraintHorizontal_bias="0.507"
android:layout_marginBottom="16dp" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="@+id/guideline2" /> app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline6" />
<TextView <TextView
android:id="@+id/Heart" android:id="@+id/Heart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center"
android:text="@string/heart_n0" android:text="@string/heart_n0"
android:textAlignment="center" android:textAlignment="center"
android:gravity="center" android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:layout_marginLeft="16dp" android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline4"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintBottom_toTopOf="@+id/guideline6"
app:layout_constraintVertical_bias="0.461" app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintRight_toLeftOf="@+id/guideline" app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp" /> app:layout_constraintTop_toTopOf="@+id/guideline4"
app:layout_constraintVertical_bias="0.461" />
<android.support.constraint.Guideline <android.support.constraint.Guideline
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -236,19 +361,19 @@
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintTop_toTopOf="@+id/guideline2"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginStart="16dp" app:layout_constraintRight_toRightOf="parent"
android:layout_marginEnd="16dp"> app:layout_constraintTop_toTopOf="@+id/guideline2">
<Space <Space
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -275,7 +400,7 @@
<Space <Space
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" /> android:layout_weight="4" />
<Button <Button
android:id="@+id/button_disconnect" android:id="@+id/button_disconnect"

View file

@ -39,14 +39,16 @@
<string name="start">Start</string> <string name="start">Start</string>
<string name="stop">Stop</string> <string name="stop">Stop</string>
<string name="disconnect">Disconnect</string> <string name="disconnect">Disconnect</string>
<string name="time_n00_00_00">Time\n00:00:00</string> <string name="time_n00_00_00">00:00:00</string>
<string name="speed_n0_0_km_h">Speed\n0.0 km/h</string> <string name="speed_n0_0_km_h">0.0</string>
<string name="rpm_n0">RPM\n0</string> <string name="rpm_n0">0</string>
<string name="level_n1">Level\n1</string> <string name="level_n1">Level\n1</string>
<string name="distance_n0_0_km">Distance\n0.0 km</string> <string name="distance_n0_0_km">0.0</string>
<string name="calories_n0_cal">Calories\n0 Cal</string> <string name="calories_n0_cal">0</string>
<string name="heart_n0">Heart\n0</string> <string name="heart_n0">0</string>
<string name="power_n0_0_w">Power\n0.0 W</string> <string name="power_n0_0_w">0.0</string>
<string name="level">Level</string> <string name="level">Level</string>
<string name="local_service_started">iConsole Bluetooth active</string>
<string name="local_service_label">OpeniConsole</string>
</resources> </resources>

View file

@ -38,5 +38,4 @@
<item name="android:shadowDy">-3.5</item> <item name="android:shadowDy">-3.5</item>
<item name="android:shadowRadius">2</item> <item name="android:shadowRadius">2</item>
</style> </style>
</resources> </resources>