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"
defaultConfig {
minSdkVersion 14
minSdkVersion 16
targetSdkVersion 25
}

View file

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

View file

@ -16,37 +16,33 @@
package org.surfsite.iconsole;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.TextView;
import android.widget.Toast;
import org.surfsite.iconsole.R;
import org.surfsite.iconsole.common.logger.Log;
import java.util.Locale;
import android.util.Log;
/**
* This fragment controls Bluetooth to communicate with other devices.
@ -66,6 +62,13 @@ public class BluetoothChatFragment extends Fragment {
private Button mStopButton;
private Button mDisconnectButton;
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
*/
@ -85,6 +88,7 @@ public class BluetoothChatFragment extends Fragment {
* Member object for the chat services
*/
private BluetoothChatService mChatService = null;
private boolean mIsBound;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -118,10 +122,12 @@ public class BluetoothChatFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
if (mChatService != null) {
mChatService.stop();
mChatService.stopBT();
}
Log.d(TAG, "onDestroy()");
super.onDestroy();
}
@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
if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
// Start the Bluetooth chat services
mChatService.start();
mChatService.startBT();
}
}
}
@ -158,6 +164,54 @@ public class BluetoothChatFragment extends Fragment {
mLevel.setValue(5);
mLevel.setWrapSelectorWheel(false);
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() {
Log.d(TAG, "setupChat()");
/*
// Initialize the array adapter for the conversation thread
mConversationArrayAdapter = new ArrayAdapter<>(getActivity(), R.layout.message);
mConversationView.setAdapter(mConversationArrayAdapter);
*/
// Initialize the BluetoothChatService to perform bluetooth connections
mChatService = new BluetoothChatService(getActivity(), mHandler);
if (!mIsBound)
doBindService();
mStartButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mChatService != null)
mChatService.startIConsole();
}
});
mStopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mChatService != null)
mChatService.stopIConsole();
}
});
mDisconnectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mChatService.stop();
if (mChatService != null)
mChatService.stopBT();
}
});
mStartButton.setEnabled(false);
mStopButton.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.
*/
private void sendMessage(String message) {
if (mChatService == null)
return;
// Check that we're actually connected before trying anything
if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
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
*/
private final Handler mHandler = new Handler() {
@SuppressLint("DefaultLocale")
@Override
public void handleMessage(Message msg) {
FragmentActivity activity = getActivity();
@ -275,12 +341,14 @@ public class BluetoothChatFragment extends Fragment {
mStartButton.setEnabled(true);
mStopButton.setEnabled(true);
mDisconnectButton.setEnabled(true);
mLevel.setEnabled(true);
break;
case BluetoothChatService.STATE_CONNECTING:
setStatus(R.string.title_connecting);
mStartButton.setEnabled(false);
mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false);
mLevel.setEnabled(false);
break;
case BluetoothChatService.STATE_LISTEN:
case BluetoothChatService.STATE_NONE:
@ -288,19 +356,22 @@ public class BluetoothChatFragment extends Fragment {
mStartButton.setEnabled(false);
mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false);
mLevel.setEnabled(false);
break;
}
break;
case Constants.MESSAGE_DATA:
if (!(msg.obj instanceof IConsole.Data))
return;
IConsole.Data data = (IConsole.Data) msg.obj;
// FIXME
// insert text here
/*
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",
data.mTime, data.mSpeed10 / 10.0, data.mPower10 / 10.0, data.mRPM,
data.mLevel, data.mDistance10 / 10.0, data.mCalories, data.mHF);
*/
//mConversationArrayAdapter.add(mConnectedDeviceName + ": " + dataMessage);
mSpeedText.setText(String.format("Speed\n% 3.1f", data.mSpeed10 / 10.0));
mPowerText.setText(String.format("Power\n% 3.1f", data.mPower10 / 10.0));
mRPMText.setText(String.format("RPM\n%d", data.mRPM));
mDistanceText.setText(String.format("Distance\n% 3.1f", data.mDistance10 / 10.0));
mCaloriesText.setText(String.format("Calories\n% 3.1f", data.mSpeed10 / 10.0));
mHFText.setText(String.format("Heart\n%d", data.mHF));
mTimeText.setText(String.format("Time:\n%s",data.getTimeStr()));
mLevel.setValue(data.mLevel);
break;
case Constants.MESSAGE_WRITE:
//byte[] writeBuf = (byte[]) msg.obj;
@ -374,6 +445,7 @@ public class BluetoothChatFragment extends Fragment {
// Get the BluetoothDevice object
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
// Attempt to connect to the device
if (mChatService != null)
mChatService.connect(device);
}

View file

@ -16,20 +16,26 @@
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.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import org.surfsite.iconsole.common.logger.Log;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.UUID;
/**
@ -38,7 +44,7 @@ import java.util.UUID;
* incoming connections, a thread for connecting with a device, and a
* thread for performing data transmissions when connected.
*/
public class BluetoothChatService {
public class BluetoothChatService extends Service {
// Debugging
private static final String TAG = "BluetoothChatService";
// 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");
// Member fields
private final BluetoothAdapter mAdapter;
private final Handler mHandler;
private Handler mHandler;
private ConnectThread mConnectThread;
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_CONNECTING = 2; // now initiating an outgoing connection
public static final int STATE_CONNECTED = 3; // now connected to a remote device
private NotificationManager mNM;
/**
* Constructor. Prepares a new BluetoothChat session.
*
* @param context The UI Activity Context
* @param handler A Handler to send messages back to the UI Activity
*/
public BluetoothChatService(Context context, Handler handler) {
private int NOTIFICATION = R.string.local_service_started;
public BluetoothChatService() {
super();
mAdapter = BluetoothAdapter.getDefaultAdapter();
mState = STATE_NONE;
mNewState = mState;
}
public class BluetoothChatServiceI extends Binder {
BluetoothChatService getService() {
return BluetoothChatService.this;
}
void setHandler(Handler 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();
}
/**
* Return the current connection state.
*/
public synchronized int getState() {
return mState;
@Override
public void onCreate() {
super.onCreate();
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
}
/**
* Start the chat service. Specifically start AcceptThread to begin a
* 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;
boolean startIConsole() {
return mConnectedThread.startIConsole();
}
// Cancel any thread currently running a connection
if (mConnectedThread != null) {
mConnectedThread.cancel();
mConnectedThread = null;
boolean stopIConsole() {
return mConnectedThread.stopIConsole();
}
// Update UI title
updateUserInterfaceTitle();
boolean setLevel(int level) {
return mConnectedThread.setLevel(level);
}
/**
@ -146,6 +139,100 @@ public class BluetoothChatService {
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
*
@ -185,8 +272,8 @@ public class BluetoothChatService {
/**
* Stop all threads
*/
public synchronized void stop() {
Log.d(TAG, "stop");
public synchronized void stopBT() {
Log.d(TAG, "stopBT");
if (mConnectThread != null) {
mConnectThread.cancel();
@ -203,13 +290,6 @@ public class BluetoothChatService {
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.
@ -227,7 +307,7 @@ public class BluetoothChatService {
updateUserInterfaceTitle();
// Start the service over to restart listening mode
BluetoothChatService.this.start();
BluetoothChatService.this.startBT();
}
/**
@ -246,7 +326,7 @@ public class BluetoothChatService {
updateUserInterfaceTitle();
// 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
* succeeds or fails.
*/
private class ConnectThread extends Thread {
class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
@ -318,7 +398,7 @@ public class BluetoothChatService {
* This thread runs during a connection with a remote device.
* It handles all incoming and outgoing transmissions.
*/
private class ConnectedThread extends Thread {
class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;

View file

@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
@ -33,7 +34,6 @@ import android.widget.ListView;
import android.widget.TextView;
import org.surfsite.iconsole.R;
import org.surfsite.iconsole.common.logger.Log;
import java.util.Set;
@ -153,7 +153,7 @@ public class DeviceListActivity extends Activity {
// Turn on sub-title for new devices
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()) {
mBtAdapter.cancelDiscovery();
}

View file

@ -1,12 +1,14 @@
package org.surfsite.iconsole;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
import org.surfsite.iconsole.common.logger.Log;
/**
* Created by harald on 25.04.17.
@ -52,7 +54,8 @@ class IConsole {
private final DataListener mDataListener;
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.mOutputStream = outputStream;
this.mDataListener = dataListener;
@ -90,6 +93,25 @@ class IConsole {
this.mPower10 = 100 * (bytes[16] - 1) + bytes[17] - 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 {
@ -127,7 +149,7 @@ class IConsole {
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();
if ((now - mTimesent) < ((mCurrentState == State.READ) ? 500 : 200)) {
@ -136,6 +158,7 @@ class IConsole {
// Flush input stream
try {
//noinspection ResultOfMethodCallIgnored
mInputStream.skip(mInputStream.available());
} catch (IOException e) {
; // ignore
@ -263,6 +286,7 @@ class IConsole {
//Log.d(TAG, "processIOAck next state");
if(mCurrentState == State.READ)
if (null != mDataListener)
mDataListener.onData(new Data(got));
mCurrentState = mNextState;
@ -318,6 +342,7 @@ class IConsole {
}
} catch (Exception e) {
Log.e(TAG, "processIO", e);
if (null != mDataListener)
mDataListener.onError(e);
return false;
}

View file

@ -17,18 +17,22 @@
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.IBinder;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.WindowManager;
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
@ -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,
* 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";
@ -55,6 +59,12 @@ public class MainActivity extends SampleActivityBase {
transaction.replace(R.id.sample_content_fragment, fragment);
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
@ -66,7 +76,7 @@ public class MainActivity extends SampleActivityBase {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
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);
return super.onPrepareOptionsMenu(menu);
@ -90,9 +100,7 @@ public class MainActivity extends SampleActivityBase {
*/
return super.onOptionsItemSelected(item);
}
/** Create a chain of targets that will receive log data */
@Override
/*
public void initializeLogging() {
// Wraps Android's native log framework.
LogWrapper logWrapper = new LogWrapper();
@ -110,4 +118,5 @@ public class MainActivity extends SampleActivityBase {
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: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
android:id="@+id/sample_content_fragment"
android:layout_weight="2"

View file

@ -20,28 +20,9 @@
android:layout_height="match_parent"
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
android:id="@+id/sample_content_fragment"
android:layout_weight="2"
android:layout_weight="4"
android:layout_width="match_parent"
android:layout_height="0px" />

View file

@ -22,147 +22,272 @@
tools:layout_editor_absoluteY="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
android:id="@+id/Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center"
android:text="@string/time_n00_00_00"
android:textAlignment="center"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline5"
app:layout_constraintHorizontal_bias="0.501"
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"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/guideline4"
android:layout_marginTop="16dp" />
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/Speed"
android:layout_width="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_marginTop="16dp"
android:fontFamily="monospace"
android:gravity="center"
android:text="@string/speed_n0_0_km_h"
android:textAlignment="center"
app:layout_constraintTop_toTopOf="@+id/guideline4"
android:layout_marginBottom="16dp"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline6"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
android:layout_marginRight="16dp" />
app:layout_constraintTop_toTopOf="@+id/guideline4" />
<TextView
android:id="@+id/Power"
android:layout_width="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:fontFamily="monospace"
android:gravity="center"
android:text="@string/power_n0_0_w"
android:textAlignment="center"
android:gravity="center"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline5"
android:layout_marginLeft="16dp"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/guideline4" />
app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline5" />
<TextView
android:id="@+id/RPM"
android:layout_width="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:fontFamily="monospace"
android:gravity="center"
android:text="@string/rpm_n0"
android:textAlignment="center"
android:gravity="center"
app:layout_constraintTop_toTopOf="@+id/guideline6"
android:layout_marginTop="16dp"
android:layout_marginLeft="16dp"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2" />
app:layout_constraintTop_toTopOf="@+id/guideline6" />
<TextView
android:id="@+id/Distance"
android:layout_width="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:fontFamily="monospace"
android:gravity="center"
android:text="@string/distance_n0_0_km"
android:textAlignment="center"
android:gravity="center"
app:layout_constraintTop_toTopOf="@+id/guideline5"
android:layout_marginTop="16dp"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
android:layout_marginBottom="16dp"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintVertical_bias="0.538"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintRight_toLeftOf="@+id/guideline"
android:layout_marginRight="16dp" />
app:layout_constraintTop_toTopOf="@+id/guideline5"
app:layout_constraintVertical_bias="0.538" />
<TextView
android:id="@+id/Calories"
android:layout_width="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:fontFamily="monospace"
android:gravity="center"
android:text="@string/calories_n0_cal"
android:textAlignment="center"
android:gravity="center"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline6"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="parent"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline2"
app:layout_constraintHorizontal_bias="0.507"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/guideline2" />
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline6" />
<TextView
android:id="@+id/Heart"
android:layout_width="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:fontFamily="monospace"
android:gravity="center"
android:text="@string/heart_n0"
android:textAlignment="center"
android:gravity="center"
android:layout_marginLeft="16dp"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
app:layout_constraintTop_toTopOf="@+id/guideline4"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/guideline6"
app:layout_constraintVertical_bias="0.461"
app:layout_constraintLeft_toLeftOf="@+id/guideline3"
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:layout_width="wrap_content"
@ -236,19 +361,19 @@
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
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"
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"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline2">
<Space
android:layout_width="wrap_content"
@ -275,7 +400,7 @@
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_weight="4" />
<Button
android:id="@+id/button_disconnect"

View file

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

View file

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