diff options
Diffstat (limited to 'app')
34 files changed, 1559 insertions, 0 deletions
diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..6a4dade --- /dev/null +++ b/app/app.iml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="fiken-display-android" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="android-gradle" name="Android-Gradle"> + <configuration> + <option name="GRADLE_PROJECT_PATH" value=":app" /> + </configuration> + </facet> + <facet type="android" name="Android"> + <configuration> + <option name="SELECTED_BUILD_VARIANT" value="debug" /> + <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> + <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> + <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" /> + <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> + <option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugTestSources" /> + <option name="ALLOW_USER_CONFIGURATION" value="false" /> + <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> + <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> + <option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" /> + <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> + <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> + <excludeFolder url="file://$MODULE_DIR$/build/tmp" /> + </content> + <orderEntry type="jdk" jdkName="Android API 18 Platform" jdkType="Android SDK" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..4073375 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 18 + buildToolsVersion "19.1.0" + + defaultConfig { + applicationId "no.topi.fiken.display" + minSdkVersion 18 + targetSdkVersion 18 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..875c86a --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/trygvis/opt/android-sdk-linux/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/no/topi/fiken/display/ApplicationTest.java b/app/src/androidTest/java/no/topi/fiken/display/ApplicationTest.java new file mode 100644 index 0000000..c306301 --- /dev/null +++ b/app/src/androidTest/java/no/topi/fiken/display/ApplicationTest.java @@ -0,0 +1,13 @@ +package no.topi.fiken.display; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> + */ +public class ApplicationTest extends ApplicationTestCase<Application> { + public ApplicationTest() { + super(Application.class); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..35956d8 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="no.topi.fiken.display" > + + <!-- + Declare this required feature if you want to make the app available to BLE-capable + devices only. If you want to make your app available to devices that don't support BLE, + you should omit this in the manifest. Instead, determine BLE capability by using + PackageManager.hasSystemFeature(FEATURE_BLUETOOTH_LE) + --> + <uses-feature + android:name="android.hardware.bluetooth_le" + android:required="true" /> + + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + + <!-- + <application android:label="@string/app_name" + android:icon="@drawable/ic_launcher" + android:theme="@android:style/Theme.Holo.Light"> + <activity android:name=".DeviceScanActivity" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".DeviceControlActivity"/> + <service android:name=".BluetoothLeService" android:enabled="true"/> + </application> + --> + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" > + <activity + android:name="no.topi.fiken.display.MainActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity + android:name="no.topi.fiken.display.DisplayControlActivity" + android:label="@string/title_activity_display_control" /> + + <service + android:name=".DefaultDisplayService" + android:enabled="true" /> + </application> + +</manifest> diff --git a/app/src/main/java/no/topi/fiken/display/Constants.java b/app/src/main/java/no/topi/fiken/display/Constants.java new file mode 100644 index 0000000..f568e1c --- /dev/null +++ b/app/src/main/java/no/topi/fiken/display/Constants.java @@ -0,0 +1,10 @@ +package no.topi.fiken.display; + +import java.util.UUID; + +public interface Constants { + String TRYGVIS_IO_BASE_UUID = "32D0xxxx-035D-59C5-70D3-BC8E4A1FD83F"; + UUID TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID = UUID.fromString(TRYGVIS_IO_BASE_UUID.replace("xxxx", "0001")); + UUID TRYGVIS_IO_GAUGE_UUID = UUID.fromString(TRYGVIS_IO_BASE_UUID.replace("xxxx", "0002")); + UUID TRYGVIS_IO_LED_UUID = UUID.fromString(TRYGVIS_IO_BASE_UUID.replace("xxxx", "0003")); +} diff --git a/app/src/main/java/no/topi/fiken/display/DefaultDisplayService.java b/app/src/main/java/no/topi/fiken/display/DefaultDisplayService.java new file mode 100644 index 0000000..b1aa2e7 --- /dev/null +++ b/app/src/main/java/no/topi/fiken/display/DefaultDisplayService.java @@ -0,0 +1,255 @@ +package no.topi.fiken.display; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; +import android.widget.Toast; + +public class DefaultDisplayService extends Service implements DisplayService { + private final Context context = DefaultDisplayService.this; + private final static String TAG = DefaultDisplayService.class.getSimpleName(); + + private final IBinder binder = new LocalBinder(this); + + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothGattService displayService; + private BluetoothGatt gatt; + + private Handler handler; + private int UPDATE_RSSI_DELAY = 1000; + + private Runnable updateRssi = new Runnable() { + @Override + public void run() { + if(gatt != null) { + gatt.readRemoteRssi(); + } + + handler.postDelayed(this, UPDATE_RSSI_DELAY); + } + }; + + public static enum ServiceState { + BROKEN, + IDLE, + SCANNING, + CONNECTED, + } + + private ServiceState serviceState = ServiceState.BROKEN; + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onCreate() { + handler = new Handler(); + + handler.postDelayed(updateRssi, UPDATE_RSSI_DELAY); + } + + public boolean initialize() { + // For API level 18 and above, get a reference to BluetoothAdapter through + // BluetoothManager. + if (mBluetoothManager == null) { + mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + Log.e(TAG, "Unable to initialize BluetoothManager."); + return false; + } + } + + mBluetoothAdapter = mBluetoothManager.getAdapter(); + if (mBluetoothAdapter == null) { + Log.e(TAG, "Unable to obtain a BluetoothAdapter."); + return false; + } + + Log.e(TAG, "Bluetooth initialized"); + + serviceState = ServiceState.IDLE; + + return true; + } + + @Override + public boolean connect(final String address) { + if (serviceState != ServiceState.IDLE) { + if (!(serviceState == ServiceState.CONNECTED && gatt.getDevice().getAddress().equals(address))) { + Log.e(TAG, "connect(): Not idle: " + serviceState); + return false; + } + + Log.i(TAG, "connect(): already connected: " + serviceState); + return true; + } + + BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); + + gatt = device.connectGatt(this, false, new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { +// Toast.makeText(context, "Connected", Toast.LENGTH_SHORT).show(); + if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) { + boolean ok = gatt.discoverServices(); + + if (!ok) { + disconnect(); + } else { + Intent intent = IntentAction.DEVICE_UPDATE.intent(); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); + intent.putExtra(IntentExtra.CONNECTED.name(), true); + sendBroadcast(intent); + } + } else { + Log.w(TAG, "Could not connect to device"); +// Toast.makeText(context, "Could not connect to device", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + Log.i(TAG, "onServicesDiscovered"); + + Log.i(TAG, "Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID = " + Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID); + + for (BluetoothGattService bluetoothGattService : gatt.getServices()) { + Log.i(TAG, "bluetoothGattService.getUuid() = " + bluetoothGattService.getUuid()); + } + + displayService = gatt.getService(Constants.TRYGVIS_IO_FIKEN_STATUS_PANEL_UUID); + + Log.i(TAG, "service=" + displayService); + + Intent intent = IntentAction.DEVICE_UPDATE.intent(); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), address); + intent.putExtra(IntentExtra.DEVICE_IS_DISPLAY.name(), displayService != null); + sendBroadcast(intent); + } + + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + Log.i(TAG, "onCharacteristicRead"); + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + Log.i(TAG, "onCharacteristicWrite"); + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + Log.i(TAG, "onCharacteristicChanged"); + } + + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + Log.i(TAG, "onDescriptorRead"); + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + Log.i(TAG, "onDescriptorWrite"); + } + + @Override + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + Log.i(TAG, "onReliableWriteCompleted"); + } + + @Override + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + Log.i(TAG, "onReadRemoteRssi, status=" + status + ", rssi=" + rssi); + if (status == BluetoothGatt.GATT_SUCCESS) { + Intent intent = IntentAction.DEVICE_UPDATE.intent(); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), gatt.getDevice().getAddress()); + intent.putExtra(IntentExtra.RSSI.name(), rssi); + sendBroadcast(intent); + } + } + }); + + if (gatt != null) { + serviceState = ServiceState.CONNECTED; + return true; + } else { + return false; + } + } + + @Override + public void disconnect() { + if (serviceState != ServiceState.CONNECTED) { + Log.d(TAG, "disconnect(): Not connected: " + serviceState); + return; + } + + serviceState = ServiceState.IDLE; + + if (gatt != null) { + try { + gatt.disconnect(); + } catch (Exception e) { + Log.w(TAG, "gatt.disconnect()", e); + } + try { + gatt.close(); + } catch (Exception e) { + Log.w(TAG, "gatt.close()", e); + } + gatt = null; + } + + displayService = null; + } + + @Override + public void startScan() { + if (serviceState != ServiceState.IDLE) { + Toast.makeText(context, "startScan(): Not idle", Toast.LENGTH_SHORT).show(); + return; + } + + serviceState = ServiceState.SCANNING; + + mBluetoothAdapter.startLeScan(leScanCallback); + } + + @Override + public void stopScan() { + Log.d(TAG, "stopScan(): stopping scanning"); + + if (serviceState != ServiceState.SCANNING) { + Log.d(TAG, "stopScan(): not scanning"); + return; + } + mBluetoothAdapter.stopLeScan(leScanCallback); + serviceState = ServiceState.IDLE; + } + + private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { + @Override + public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) { + Log.i(TAG, "onLeScan()"); + Intent intent = IntentAction.DEVICE_UPDATE.intent(); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), device.getAddress()); + intent.putExtra(IntentExtra.DEVICE_NAME.name(), device.getName()); + intent.putExtra(IntentExtra.RSSI.name(), rssi); + sendBroadcast(intent); + } + }; +} diff --git a/app/src/main/java/no/topi/fiken/display/DisplayControlActivity.java b/app/src/main/java/no/topi/fiken/display/DisplayControlActivity.java new file mode 100644 index 0000000..1147c0c --- /dev/null +++ b/app/src/main/java/no/topi/fiken/display/DisplayControlActivity.java @@ -0,0 +1,141 @@ +package no.topi.fiken.display; + +import android.app.Activity; +import android.content.BroadcastReceiver; +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.view.Menu; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import static java.lang.String.valueOf; +import static no.topi.fiken.display.DisplayService.DeviceInfo; +import static no.topi.fiken.display.DisplayService.IntentAction; +import static no.topi.fiken.display.DisplayService.IntentExtra; +import static no.topi.fiken.display.DisplayService.LocalBinder; + +public class DisplayControlActivity extends Activity { + private final static String TAG = DisplayControlActivity.class.getSimpleName(); + + private DisplayService displayService; + private DeviceInfo deviceInfo; + + private TextView deviceNameView; + private TextView deviceRssiView; + private LinearLayout gaugesLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_display_control); + + final Intent intent = getIntent(); + String deviceAddress = intent.getStringExtra(IntentExtra.DEVICE_ADDRESS.name()); + deviceInfo = new DeviceInfo(deviceAddress, 0); + deviceInfo.name = intent.getStringExtra(IntentExtra.DEVICE_NAME.name()); + + Intent displayServiceIntent = new Intent(this, DefaultDisplayService.class); + bindService(displayServiceIntent, serviceConnection, BIND_AUTO_CREATE); + + deviceNameView = (TextView) findViewById(R.id.device_name); + deviceRssiView = (TextView) findViewById(R.id.device_rssi); + gaugesLayout = (LinearLayout) findViewById(R.id.gauges); + + Button disconnectButton = (Button) findViewById(R.id.button_disconnect); + disconnectButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + disconnect(); + } + }); + + updateValues(); + } + + private void updateValues() { + deviceNameView.setText(deviceInfo.name != null ? deviceInfo.name : getText(R.string.name_unknown)); + deviceRssiView.setText(getText(R.string.rssi) + ": " + (deviceInfo.rssi != 0 ? valueOf(deviceInfo.rssi) : "")); + } + + @Override + protected void onResume() { + super.onResume(); + registerReceiver(displayServiceBroadcastReceiver, IntentAction.ALL_FILTER); + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(displayServiceBroadcastReceiver); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + displayService.disconnect(); + unbindService(serviceConnection); + displayService = null; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_display_control, menu); + return true; + } + + public void disconnect() { + if (displayService != null) { + displayService.disconnect(); + } + + finish(); + } + + private final ServiceConnection serviceConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName componentName, IBinder service) { + displayService = ((LocalBinder) service).getService(); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + displayService = null; + } + }; + + private final BroadcastReceiver displayServiceBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + + IntentAction action = IntentAction.valueOf(intent); + + String deviceAddress = intent.getStringExtra(IntentExtra.DEVICE_ADDRESS.name()); + + if (action == IntentAction.DEVICE_UPDATE && deviceInfo.address.equals(deviceAddress)) { + runOnUiThread(new Runnable() { + @Override + public void run() { + deviceInfo.update(intent); + + if (intent.hasExtra(IntentExtra.CONNECTED.name())) { + boolean connected = intent.getBooleanExtra(IntentExtra.CONNECTED.name(), false); + + if (!connected) { + finish(); + } + } + updateValues(); + } + }); + } + } + }; +} diff --git a/app/src/main/java/no/topi/fiken/display/DisplayService.java b/app/src/main/java/no/topi/fiken/display/DisplayService.java new file mode 100644 index 0000000..c907138 --- /dev/null +++ b/app/src/main/java/no/topi/fiken/display/DisplayService.java @@ -0,0 +1,102 @@ +package no.topi.fiken.display; + +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Binder; + +public interface DisplayService { + + enum IntentExtra { + DEVICE_NAME, + DEVICE_ADDRESS, + DEVICE_IS_DISPLAY, + RSSI, + SCANNING, + CONNECTED, + } + + public static enum IntentAction { + DEVICE_UPDATE; + + private final String key = getClass().getName() + "." + name(); + + public Intent intent() { + return new Intent(key); + } + + public static final IntentFilter ALL_FILTER = new IntentFilter() {{ + for (DefaultDisplayService.IntentAction intentAction : DefaultDisplayService.IntentAction.values()) { + addAction(intentAction.key); + } + }}; + + public static IntentAction valueOf(Intent intent) { + try { + return valueOf(intent.getAction().replaceAll(".*\\.", "")); + } catch (IllegalArgumentException e) { + return null; + } + } + } + + public class LocalBinder extends Binder { + private final DisplayService service; + + public LocalBinder(DisplayService service) { + this.service = service; + } + + DisplayService getService() { + return service; + } + } + + boolean initialize(); + + void startScan(); + + void stopScan(); + + boolean connect(String address); + + public void disconnect(); + + class DeviceInfo { + final String address; + int rssi = 0; + Boolean isDisplay = null; + String name; + + DeviceInfo(String address, int rssi) { + this.address = address; + this.rssi = rssi; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DeviceInfo that = (DeviceInfo) o; + + return address.equals(that.address); + } + + @Override + public int hashCode() { + return address.hashCode(); + } + + public void update(Intent intent) { + if (intent.hasExtra(IntentExtra.DEVICE_IS_DISPLAY.name())) { + isDisplay = intent.getBooleanExtra(IntentExtra.DEVICE_IS_DISPLAY.name(), false); + } + if (intent.hasExtra(IntentExtra.RSSI.name())) { + rssi = intent.getIntExtra(IntentExtra.RSSI.name(), 0); + } + if (intent.hasExtra(IntentExtra.DEVICE_NAME.name())) { + name = intent.getStringExtra(IntentExtra.DEVICE_NAME.name()); + } + } + } +} diff --git a/app/src/main/java/no/topi/fiken/display/ExceptionHandler.java b/app/src/main/java/no/topi/fiken/display/ExceptionHandler.java new file mode 100644 index 0000000..3d4560b --- /dev/null +++ b/app/src/main/java/no/topi/fiken/display/ExceptionHandler.java @@ -0,0 +1,22 @@ +package no.topi.fiken.display; + +import android.util.Log; + +public class ExceptionHandler implements Thread.UncaughtExceptionHandler { + private final static String TAG = DefaultDisplayService.class.getSimpleName(); + + public static final ExceptionHandler EXCEPTION_HANDLER = new ExceptionHandler(); + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + Log.e(TAG, "Uncaught", ex); + + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + if (ex instanceof Error) { + throw (Error) ex; + } + throw new RuntimeException(ex); + } +} diff --git a/app/src/main/java/no/topi/fiken/display/MainActivity.java b/app/src/main/java/no/topi/fiken/display/MainActivity.java new file mode 100644 index 0000000..d9dc073 --- /dev/null +++ b/app/src/main/java/no/topi/fiken/display/MainActivity.java @@ -0,0 +1,335 @@ +package no.topi.fiken.display; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.ListActivity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.List; + +import static java.lang.String.valueOf; +import static no.topi.fiken.display.DisplayService.IntentAction; +import static no.topi.fiken.display.DisplayService.IntentExtra; +import static no.topi.fiken.display.ExceptionHandler.EXCEPTION_HANDLER; + +public class MainActivity extends ListActivity { + private final static String TAG = MainActivity.class.getSimpleName(); + + // Stops scanning after 10 seconds. + private static final long SCAN_PERIOD = 3 * 1000; + + private static final int REQUEST_ENABLE_BT = 1; + + private DisplayListAdapter displayList; + private Handler handler; + private BluetoothAdapter mBluetoothAdapter; + private boolean mScanning; + private DisplayService displayService; + private String deviceToShow; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "onCreate"); + Thread.setDefaultUncaughtExceptionHandler(EXCEPTION_HANDLER); + super.onCreate(savedInstanceState); + + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.title_devices); + } + handler = new Handler(); + + // Use this check to determine whether BLE is supported on the device. Then you can + // selectively disable BLE-related features. + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { + Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); + finish(); + } + + // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to + // BluetoothAdapter through BluetoothManager. + final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + mBluetoothAdapter = bluetoothManager.getAdapter(); + + // Checks if Bluetooth is supported on the device. + if (mBluetoothAdapter == null) { + Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show(); + finish(); + } + + ServiceConnection serviceConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName componentName, IBinder service) { + displayService = ((DisplayService.LocalBinder) service).getService(); + if (!displayService.initialize()) { + finish(); + } + + startScan(); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + stopScan(); + displayService = null; + } + }; + + Intent displayServiceIntent = new Intent(this, DefaultDisplayService.class); + bindService(displayServiceIntent, serviceConnection, BIND_AUTO_CREATE); + } + + @Override + protected void onResume() { + Log.i(TAG, "onResume"); + + super.onResume(); + + // Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled, + // fire an intent to display a dialog asking the user to grant permission to enable it. + if (!mBluetoothAdapter.isEnabled()) { + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } + + registerReceiver(displayServiceBroadcastReceiver, IntentAction.ALL_FILTER); + } + + @Override + protected void onPause() { + Log.i(TAG, "onPause"); + + super.onPause(); + stopScan(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.i(TAG, "onActivityResult"); + + // User chose not to enable Bluetooth. + if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) { + finish(); + return; + } + + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + Log.i(TAG, "onCreateOptionsMenu"); + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + + if (!mScanning) { + menu.findItem(R.id.menu_stop).setVisible(false); + menu.findItem(R.id.menu_scan).setVisible(true); + menu.findItem(R.id.menu_refresh).setActionView(null); + } else { + menu.findItem(R.id.menu_stop).setVisible(true); + menu.findItem(R.id.menu_scan).setVisible(false); + menu.findItem(R.id.menu_refresh).setActionView(R.layout.actionbar_indeterminate_progress); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Log.i(TAG, "onOptionsItemSelected"); + + switch (item.getItemId()) { + case R.id.menu_scan: + startScan(); + break; + case R.id.menu_stop: + stopScan(); + break; + } + return super.onOptionsItemSelected(item); + } + + private void startScan() { + displayList = new DisplayListAdapter(); + setListAdapter(displayList); + + displayService.startScan(); + + // Stops scanning after a pre-defined scan period. + handler.postDelayed(new Runnable() { + @Override + public void run() { + displayService.stopScan(); + invalidateOptionsMenu(); + } + }, SCAN_PERIOD); + } + + private void stopScan() { + if (displayService != null) { + displayService.stopScan(); + } + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + stopScan(); + + DisplayService.DeviceInfo state = displayList.getDevice(position); + + if (!displayService.connect(state.address)) { + Toast.makeText(this, "Could not connect to " + state.address, Toast.LENGTH_SHORT).show(); + } else { + deviceToShow = state.address; + } + } + + private final BroadcastReceiver displayServiceBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + runOnUiThread(new Runnable() { + @Override + public void run() { + IntentAction action = IntentAction.valueOf(intent); + + String deviceAddress = intent.getStringExtra(IntentExtra.DEVICE_ADDRESS.name()); + + if (action == IntentAction.DEVICE_UPDATE && deviceAddress != null) { + DisplayService.DeviceInfo device = displayList.getDevice(deviceAddress, true); + + device.update(intent); + + if (intent.hasExtra(IntentExtra.CONNECTED.name())) { + boolean connected = intent.getBooleanExtra(IntentExtra.CONNECTED.name(), false); + + if (connected) { + if (deviceToShow != null && deviceToShow.equals(device.address)) { + Log.i(TAG, "connected to " + deviceToShow); + final Intent intent = new Intent(context, DisplayControlActivity.class); + intent.putExtra(IntentExtra.DEVICE_ADDRESS.name(), device.address); + intent.putExtra(IntentExtra.DEVICE_NAME.name(), device.name); + startActivity(intent); + } + } + } + displayList.notifyDataSetChanged(); + } + + if (intent.hasExtra(IntentExtra.SCANNING.name())) { + mScanning = intent.getBooleanExtra(IntentExtra.SCANNING.name(), false); + invalidateOptionsMenu(); + } + } + }); + } + }; + + static class ViewHolder { + final TextView deviceName; + final TextView deviceAddress; + final TextView rssi; + final TextView isDisplay; + + ViewHolder(TextView deviceName, TextView deviceAddress, TextView rssi, TextView isDisplay) { + this.deviceName = deviceName; + this.deviceAddress = deviceAddress; + this.rssi = rssi; + this.isDisplay = isDisplay; + } + } + + private class DisplayListAdapter extends BaseAdapter { + private List<DisplayService.DeviceInfo> devices = new ArrayList<DisplayService.DeviceInfo>(); + private LayoutInflater inflater = MainActivity.this.getLayoutInflater(); + + public DisplayService.DeviceInfo getDevice(int position) { + return devices.get(position); + } + + public DisplayService.DeviceInfo getDevice(String address, boolean create) { + for (DisplayService.DeviceInfo device : devices) { + if (device.address.equals(address)) { + return device; + } + } + + DisplayService.DeviceInfo deviceInfo = null; + if (create) { + deviceInfo = new DisplayService.DeviceInfo(address, 0); + devices.add(deviceInfo); + } + return deviceInfo; + } + + @Override + public int getCount() { + return devices.size(); + } + + @Override + public Object getItem(int i) { + return devices.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + ViewHolder viewHolder; + + if (view == null) { + view = inflater.inflate(R.layout.listitem_device, null); + viewHolder = new ViewHolder( + (TextView) view.findViewById(R.id.device_name), + (TextView) view.findViewById(R.id.device_address), + (TextView) view.findViewById(R.id.device_rssi), + (TextView) view.findViewById(R.id.device_isDisplay)); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + + DisplayService.DeviceInfo state = devices.get(i); + if (state.name != null && state.name.length() > 0) { + viewHolder.deviceName.setText(state.name); + } else { + viewHolder.deviceName.setText(R.string.unknown_device); + } + viewHolder.deviceAddress.setText(state.address); + + viewHolder.rssi.setText(getText(R.string.rssi) + ": " + + (state.rssi != 0 ? valueOf(state.rssi) : getText(R.string.rssi_unknown))); + + viewHolder.isDisplay.setText("Is display: " + + (state.isDisplay != null ? state.isDisplay : "unknown")); + view.setClickable(state.isDisplay != null && state.isDisplay); + + return view; + } + } +} diff --git a/app/src/main/res/demo/demo/activity_main2.xml b/app/src/main/res/demo/demo/activity_main2.xml new file mode 100644 index 0000000..b48f349 --- /dev/null +++ b/app/src/main/res/demo/demo/activity_main2.xml @@ -0,0 +1,36 @@ +<!-- + 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout style="@style/Widget.SampleMessageTile" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView style="@style/Widget.SampleMessage" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/horizontal_page_margin" + android:layout_marginRight="@dimen/horizontal_page_margin" + android:layout_marginTop="@dimen/vertical_page_margin" + android:layout_marginBottom="@dimen/vertical_page_margin" + android:text="@string/intro_message" /> + </LinearLayout> +</LinearLayout> diff --git a/app/src/main/res/demo/values/base-strings.xml b/app/src/main/res/demo/values/base-strings.xml new file mode 100644 index 0000000..6071269 --- /dev/null +++ b/app/src/main/res/demo/values/base-strings.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<resources> +<!-- + <string name="app_name">BluetoothLeGatt</string> +--> + <string name="intro_message"> + <![CDATA[ + + + This sample demonstrates how to use the Bluetooth LE Generic Attribute Profile (GATT) + to transmit arbitrary data between devices. + + + ]]> + </string> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/demo/values/template-dimens.xml b/app/src/main/res/demo/values/template-dimens.xml new file mode 100644 index 0000000..afacaf5 --- /dev/null +++ b/app/src/main/res/demo/values/template-dimens.xml @@ -0,0 +1,32 @@ +<!-- + 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. + --> + +<resources> + + <!-- Define standard dimensions to comply with Holo-style grids and rhythm. --> + + <dimen name="margin_tiny">4dp</dimen> + <dimen name="margin_small">8dp</dimen> + <dimen name="margin_medium">16dp</dimen> + <dimen name="margin_large">32dp</dimen> + <dimen name="margin_huge">64dp</dimen> + + <!-- Semantic definitions --> + + <dimen name="horizontal_page_margin">@dimen/margin_medium</dimen> + <dimen name="vertical_page_margin">@dimen/margin_medium</dimen> + +</resources>
\ No newline at end of file diff --git a/app/src/main/res/demo/values/template-styles.xml b/app/src/main/res/demo/values/template-styles.xml new file mode 100644 index 0000000..f23e542 --- /dev/null +++ b/app/src/main/res/demo/values/template-styles.xml @@ -0,0 +1,44 @@ +<!-- + 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. + --> + +<resources> + + <!-- Activity themes --> + + <style name="Theme.Base" parent="android:Theme.Light"/> + + <style name="Theme.Sample" parent="Theme.Base"/> + + <!-- + <style name="AppTheme" parent="Theme.Sample" /> + --> + <!-- Widget styling --> + + <style name="Widget"/> + + <style name="Widget.SampleMessage"> + <item name="android:textAppearance">?android:textAppearanceMedium</item> + <item name="android:lineSpacingMultiplier">1.1</item> + </style> + + <style name="Widget.SampleMessageTile"> + <item name="android:background">@drawable/tile</item> + <item name="android:shadowColor">#7F000000</item> + <item name="android:shadowDy">-3.5</item> + <item name="android:shadowRadius">2</item> + </style> + +</resources> diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..96a442e --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_launcher.png diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..359047d --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_launcher.png diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..71c6d76 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_launcher.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..4df1894 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/layout/actionbar_indeterminate_progress.xml b/app/src/main/res/layout/actionbar_indeterminate_progress.xml new file mode 100644 index 0000000..c68bc3c --- /dev/null +++ b/app/src/main/res/layout/actionbar_indeterminate_progress.xml @@ -0,0 +1,23 @@ +<!-- + Copyright 2013 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="wrap_content" + android:layout_width="56dp" + android:minWidth="56dp"> + <ProgressBar android:layout_width="32dp" + android:layout_height="32dp" + android:layout_gravity="center"/> +</FrameLayout> diff --git a/app/src/main/res/layout/activity_display_control.xml b/app/src/main/res/layout/activity_display_control.xml new file mode 100644 index 0000000..4151184 --- /dev/null +++ b/app/src/main/res/layout/activity_display_control.xml @@ -0,0 +1,42 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + tools:context="no.topi.fiken.display.DisplayControlActivity"> + + <TextView + android:id="@+id/device_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="24sp" + android:layout_alignParentEnd="true"/> + + <TextView + android:id="@+id/device_rssi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="12sp" + android:layout_below="@+id/device_name"/> + + <Button + android:id="@+id/button_disconnect" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/disconnect" + android:layout_below="@+id/device_rssi" + /> + + <LinearLayout + android:id="@+id/gauges" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_below="@+id/button_disconnect" + android:layout_alignParentStart="true"> + </LinearLayout> + +</RelativeLayout> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..ead92a8 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,7 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MainActivity" + tools:ignore="MergeRootFrame"/> diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml new file mode 100644 index 0000000..8958013 --- /dev/null +++ b/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,36 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" + android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + tools:context=".MainActivity$PlaceholderFragment"> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="New Button" + android:id="@+id/button" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:layout_alignParentStart="true" /> + + <ProgressBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/progressBar" + android:layout_centerVertical="true" + android:layout_centerHorizontal="true" + android:visibility="gone" /> + + <ListView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/listView" + android:layout_toStartOf="@+id/progressBar" + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" + android:layout_above="@+id/button" + android:layout_alignParentEnd="true" /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/gatt_services_characteristics.xml b/app/src/main/res/layout/gatt_services_characteristics.xml new file mode 100644 index 0000000..0156afd --- /dev/null +++ b/app/src/main/res/layout/gatt_services_characteristics.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp"> + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp"> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/label_device_address" + android:textSize="18sp"/> + <Space android:layout_width="5dp" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/device_address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="18sp"/> + </LinearLayout> + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp"> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/label_state" + android:textSize="18sp"/> + <Space android:layout_width="5dp" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/connection_state" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/disconnected" + android:textSize="18sp"/> + </LinearLayout> + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp"> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/label_data" + android:textSize="18sp"/> + <Space android:layout_width="5dp" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/data_value" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/no_data" + android:textSize="18sp"/> + </LinearLayout> + <ExpandableListView android:id="@+id/gatt_services_list" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> +</LinearLayout> diff --git a/app/src/main/res/layout/listitem_device.xml b/app/src/main/res/layout/listitem_device.xml new file mode 100644 index 0000000..54f529e --- /dev/null +++ b/app/src/main/res/layout/listitem_device.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/device_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="24sp"/> + + <TextView + android:id="@+id/device_address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="12sp"/> + + <TextView + android:id="@+id/device_rssi" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="12sp"/> + + <TextView + android:id="@+id/device_isDisplay" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="12sp"/> + +<!-- + <Button + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/connect" + android:id="@+id/button2"/> +--> + +</LinearLayout> diff --git a/app/src/main/res/menu/gatt_services.xml b/app/src/main/res/menu/gatt_services.xml new file mode 100644 index 0000000..25d64b6 --- /dev/null +++ b/app/src/main/res/menu/gatt_services.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/menu_refresh" + android:checkable="false" + android:orderInCategory="1" + android:showAsAction="ifRoom"/> + <item android:id="@+id/menu_connect" + android:title="@string/menu_connect" + android:orderInCategory="100" + android:showAsAction="ifRoom|withText"/> + <item android:id="@+id/menu_disconnect" + android:title="@string/menu_disconnect" + android:orderInCategory="101" + android:showAsAction="ifRoom|withText"/> +</menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml new file mode 100644 index 0000000..08b604e --- /dev/null +++ b/app/src/main/res/menu/main.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/menu_refresh" + android:checkable="false" + android:orderInCategory="1" + android:showAsAction="ifRoom"/> + <item android:id="@+id/menu_scan" + android:title="@string/menu_scan" + android:orderInCategory="100" + android:showAsAction="ifRoom|withText"/> + <item android:id="@+id/menu_stop" + android:title="@string/menu_stop" + android:orderInCategory="101" + android:showAsAction="ifRoom|withText"/> +</menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/menu_display_control.xml b/app/src/main/res/menu/menu_display_control.xml new file mode 100644 index 0000000..51a408b --- /dev/null +++ b/app/src/main/res/menu/menu_display_control.xml @@ -0,0 +1,8 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:context="no.topi.fiken.display.DisplayControlActivity"> + <item android:id="@+id/action_settings" + android:title="@string/action_settings" + android:orderInCategory="100" + android:showAsAction="never"/> +</menu> diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml new file mode 100644 index 0000000..87a750e --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,5 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + <item android:id="@+id/action_settings" android:title="@string/action_settings" + android:orderInCategory="100" android:showAsAction="never" /> +</menu> diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..0cea3b4 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">Display</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + <string name="ble_not_supported">BLE is not supported</string> + <string name="label_data">Data:</string> + <string name="label_device_address">Device address:</string> + <string name="label_state">State:</string> + <string name="no_data">No data</string> + <string name="connected">Connected</string> + <string name="disconnected">Disconnected</string> + <string name="title_devices">BLE Device Scan</string> + <string name="error_bluetooth_not_supported">Bluetooth not supported.</string> + <string name="unknown_device">Unknown device</string> + <string name="unknown_characteristic">Unknown characteristic</string> + <string name="unknown_service">Unknown service</string> + <string name="rssi">RSSI</string> + <string name="name_unknown">Unknown name</string> + <string name="rssi_unknown">unknown</string> + <string name="connect">Connect</string> + + <!-- Menu items --> + <string name="menu_connect">Connect</string> + <string name="menu_disconnect">Disconnect</string> + <string name="menu_scan">Scan</string> + <string name="menu_stop">Stop</string> + <string name="title_activity_display_control">DisplayControlActivity</string> + <string name="disconnect">Disconnect</string> + +</resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..ff6c9d2 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- Customize your theme here. --> + </style> + +</resources> |