On Github anteq / eestec-beacon-workshops
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Intent intent = new Intent(Activity_starting_other_activity, ActivityToBeStarted.class); startActivity(Intent intent); startActivityForResult(Intent intent, int requestCode);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent){
if(resultCode != RESULT_OK) {
finish(); //ends activity
} else {
// do something with the information retrieved
}
}
debug (Log.d)
info (Log.i)
error (Log.e)
verbose (Log.v)
warn (Log.w)
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
defaultConfig {
applicationId "com.workshops.helloworld"
minSdkVersion 18
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
...
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.workshops.helloworld" >
<uses-permission
android:name="android.permission.SOME_PERMISSION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:labelw="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".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>
</application>
</manifest>
... is a network of interconnected things ...
... embedded with electronics and sensors ...
... which enables them to achieve greater value and service ...
... by exchanging and analysing data.
// Don't type lines with "//" - these are comments!
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eestec.hotandcold" >
// Add the permissions on the top
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
// (...)
</manifest>
// MainActivity
// add only screenOrientation - the rest should already be here
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
// the following lines make our Activity default - it starts when we start the app
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
// Thermometer Activity
// add only configChanges and screenOrientation - the rest should already be here
<activity
android:name=".ThermometerActivity"
android:label="@string/title_activity_thermometer"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName=".MainActivity"
android:screenOrientation="portrait" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.eestec.hotandcold.MainActivity" />
</activity>
// Neccesary imports
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
// (...) - leave the rest unchanged
public class MainActivity extends ActionBarActivity {
private BluetoothAdapter bluetoothAdapter;
private int REQUEST_ENABLE_BT = 1; // must be greater than 0
public boolean bluetoothNotEnabled(){
return !bluetoothAdapter.isEnabled();
}
public void askForBluetoothAccess(){
Intent enableBluetoothIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BT);
}
// (...) - leave the rest unchanged
}
// MainActivity's onCreate method - it's already there
// we just change it a little bit
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // invokes parent's onCreate methon; common practice
setContentView(R.layout.activity_main); // binds xml layout with java class
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // add this...
if(bluetoothNotEnabled()) askForBluetoothAccess(); // ... and this
}
import android.widget.Button;
import android.content.Context;
// ...
public class MainActivity extends ActionBarActivity {
// declare new Objects, that we'll use in a second
private Button startButton;
private Context context = this;
//(...)
}
@Override
protected void onCreate(Bundle savedInstanceState) {
//(...)
startButton = (Button)findViewById(R.id.start_button); // this is CASTING
// findViewById returns unspecified ViewType, we have to CAST it on the one wanted
startButton.setOnClickListener(new View.OnClickListener() {
// here we set new Object to listen to the onClick events
@Override // here we OVERRIDE the existing method
public void onClick(View v) {
Intent thermometerIntent = new Intent(context, ThermometerActivity.class);
startActivity(thermometerIntent);
}
});
}
You should see an empty activity with a button, which will lead you to second empty activity.
Oh hey, these are some notes. They'll be hidden in your presentation, but you can see them if you open the speaker notes window (hit 's' on your keyboard).Turn to Project view
Add it as a library to our project
In AndroidManifest.xml
<manifest>
<application>
(...)
<service android:name="com.estimote.sdk.service.BeaconService"
android:exported="false"/>
</application>
</manifest>
package com.eestec.hotandcold;
import com.estimote.sdk.Beacon;
import com.estimote.sdk.BeaconManager;
import com.estimote.sdk.Region;
import com.estimote.sdk.Utils;
public class Distance {
// empty for now
}
Import should be marked as "unused" but it's fine.
public class Distance {
// private means that variable cannot be accessed from other Objects
// final means that the variable's value cannot be changed
private final double MAX_BEACON_DISTANCE = 100;
// volatile means that this value will be modified or accessed by different threads
private volatile double distanceToClosestBeacon;
// Constructor
public Distance(){
this.distanceToClosestBeacon = MAX_BEACON_DISTANCE;
}
}
// New imports, we'll need them in a minute
import android.util.Log;
import java.util.List;
// ...
public class Distance {
// (...)
public void calculate(BeaconManager beaconManager){
// This method will be invoked from Thermometer Activity.
// It'll basically measure the distance to the nearest beacon.
}
}
public void calculate(BeaconManager beaconManager){
beaconManager.setRangingListener(new BeaconManager.RangingListener() {
// we're inside a new RangingListener object
// here, we can override default behavior on event of discovering beacon
});
}
public void calculate(BeaconManager beaconManager){
beaconManager.setRangingListener(new BeaconManager.RangingListener() {
@Override
public void onBeaconsDiscovered(Region region, List<Beacon> beacons) {
// here, we can actually operate on Estimote API
// it takes two arguments:
// Region region - Region object, which defines which devices we listen to
// List<Beacon> beacons - list of found beacons
// we'll define the function's body on the next slide
}
});
}
public void onBeaconsDiscovered(Region region, List<Beacon> beacons) {
Integer major = 0;
Integer minor = 0;
distanceToClosestBeacon = MAX_BEACON_DISTANCE; // resetting closest beacon proximity
for (Beacon rangedBeacon : beacons) {
// Estimote provides us with function to compute distance
// based on signal's strength
Double distanceToThisBeacon = Utils.computeAccuracy(rangedBeacon);
if (distanceToThisBeacon <= distanceToClosestBeacon) {
distanceToClosestBeacon = distanceToThisBeacon;
major = rangedBeacon.getMajor(); // that's how we can get beacon's major ID
minor = rangedBeacon.getMinor(); // and that's how we can get it's minor ID
}
}
Log.d("Closest beacon found: ", Double.toString(distanceToClosestBeacon) + " away");
Log.d("Closest beacon found: ", "Its unique identifier ID is " + major.toString() + " " + minor.toString());
}
Create new BeaconManager and new Region
// Moooooooaaarrr imports
import com.estimote.sdk.BeaconManager;
import com.estimote.sdk.Region;
import android.os.RemoteException;
// ...
public class ThermometerActivity extends ActionBarActivity {
private BeaconManager beaconManager; // service responsible for scanning beacons
private static final Region ALL_ESTIMOTE_BEACONS = new Region("regionId", null, null, null);
// we define listening to all beacons
private volatile Distance distance; // instance of our Distance class
// ...
}
Create new instance of Distance object and invoke it in onCreate()
@Override
protected void onCreate(Bundle savedInstanceState) {
(...)
beaconManager = new BeaconManager(this);
distance = new Distance();
distance.calculate(beaconManager);
}
// in ThermometerActivity class
@Override
protected void onStart() {
super.onStart();
beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
// we implement onServiceReady, because ServiceReadyCallback is an interface
@Override
public void onServiceReady() {
try {
beaconManager.startRanging(ALL_ESTIMOTE_BEACONS);
} catch (RemoteException e) {
Log.e("test", "Cannot start ranging", e);
}
}
});
}
// in ThermometerActivity class
@Override
protected void onStop() {
super.onStop();
try {
beaconManager.stopRanging(ALL_ESTIMOTE_BEACONS);
} catch (RemoteException e) {
Log.e("test", "Cannot stop but it does not matter now", e);
}
beaconManager.disconnect();
}
public class Distance {
// ...
// we define getter so we can get distance from other Objects
public double getDistance(){
return distanceToClosestBeacon;
}
}
}
In activity_thermometer.xml, add new TextView.
Change its ID.
Declare TextView and distance to show.
import android.widget.TextView;
// ...
public class ThermometerActivity extends ActionBarActivity {
private TextView distanceText; // here we'll set the text for display
private volatile double currentDistance = 0; // here we'll store data from Distance class
private Thread thread; // new thread will allow us to run asynchronously
// ...
}
}
Keep reference to TextView.
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
distanceText = (TextView) findViewById(R.id.beacon_distance);
}
Start a new Thread.
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
thread = new Thread(){
// here, we're inside the new Thread object
@Override
public void run(){
// for the Thread to work properly, we need to override its run() method
}
};
thread.start(); // start it, when the activity is created
}
Get distance
@Override
public void run(){
while(???) {
try {
sleep(50);
// currentDistance = ???
// QUEST FOR YOU:
// How can we get distance from Distance Class?
// How can we "average it"?
} catch (InterruptedException e) {
e.printStackTrace(); // log information about error; common practice
}
}
}
Get distance
@Override
public void run(){
while(???) {
try {
sleep(50);
// Possible solution:
currentDistance = 0.9*currentDistance + 0.1*distance.getDistance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Format text
// ...
try {
// ...
String text;
// ANOTHER EXCITING QUEST :D
// format the distance appropriately
// e.g. when currentDistance = 1.2, set text to 1.2 meters
// and when currentDistance = 0.8, set text to 80 centimeters
}
// ...
Format text
// ...
try {
// ...
String text;
// format the distance appropriately
if (currentDistance >= 1.0){
text = String.format("%.2f", currentDistance) + " m";
} else {
text = String.format("%.2f", 100*currentDistance) + " cm";
}
}
// ...
Post it to update UI
// ...
try {
//...
final String finalText = text; // String passed to another thread has to be final
// we cannot change UI directly from another thread
// we have to communicate with main thread by post() method
distanceText.post(new Runnable() {
public void run() {
distanceText.setText(finalText); // here we change the TextView
}
});
}
// ...
But what about the while loop?
@Override
public void run(){
while(???) { // wtf is this "???"
// we need a clever boolean, so our background Thread won't change UI forever
}
}
public class ThermometerActivity extends ActionBarActivity {
// ...
private volatile boolean showProximity = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
(...)
thread = new Thread(){
@Override
public void run(){
while(showProximity) {
// ...
}
}
}
@Override
protected void onStop() {
// ...
showProximity = false;
}
}
It should work!
<ProgressBar
android:id="@+id/thermometer"
android:layout_width="30dip" <!-- dip = density independent pixels -->
android:layout_height="300dip"
style="@style/Widget.ProgressBar.Vertical" <!-- undefined, yeah... in a minute :) -->
android:indeterminate="false" <!-- that means, that our bar will be "finite" -->
android:layout_alignParentTop="true" <!-- stick it to the top of Parent -->
android:layout_centerHorizontal="true" <!-- center it -->
android:layout_marginTop="62dp" <!-- give it some margin -->
/>
<style name="Widget">
</style>
<style name="Widget.ProgressBar">
<item name="android:indeterminateOnly">true</item>
<item name="android:indeterminateBehavior">repeat</item>
<item name="android:indeterminateDuration">3500</item>
<item name="android:minWidth">48dip</item>
<item name="android:maxWidth">48dip</item>
<item name="android:minHeight">48dip</item>
<item name="android:maxHeight">48dip</item>
</style>
<style name="Widget.ProgressBar.Vertical">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@drawable/progress_bar_vertical</item>
<!-- YES, WE KNOW IT'S UNDEFINED -->
<item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item>
<item name="android:minWidth">1dip</item>
<item name="android:maxWidth">12dip</item>
</style>
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> // here we'll be creating two items </layer-list>
<item android:id="@android:id/background">
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#ff9d9e9d"
android:centerColor="#ff5a5d5a"
android:centerY="0.75"
android:endColor="#ff747674"
android:angle="180"
/>
</shape>
</item>
<item android:id="@android:id/progress">
<clip android:clipOrientation="vertical" android:gravity="bottom">
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="@color/cold" <!-- undefined -->
android:centerColor="@color/warm" <!-- undefined, too -->
android:centerY="0.75"
android:endColor="@color/hot" <!-- undefined, as well -->
android:angle="90"
/>
</shape>
</clip>
</item>
<resources>
<color name="cold">#ff0969A2</color>
<color name="warm">#ffff4f00</color>
<color name="hot">#ffff2300</color>
</resources>
import android.widget.ProgressBar;
// ...
private ProgressBar progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
progress = (ProgressBar) findViewById(R.id.thermometer);
progress.setMax(100);
// ...
}
// in Thread's run() method
@Override
public void run(){
// ...
double currentProgress; // this will control the bar's progress
double progressStatus; // helper variable
}
// inside while & try
@Override
public void run(){
// ...
while(showProximity) {
try {
// ...
progressStatus = progress.getMax() - distance.getDistance();
currentProgress = progress.getProgress();
currentProgress = Math.ceil(0.9 * currentProgress + 0.1 * progressStatus);
progress.setProgress((int)currentProgress);
}
}
// import Shader and LinearGradient
public void setGradientText(TextView amp){
Shader textShader = new LinearGradient(0, 0, 0, amp.getPaint().getTextSize(),
new int[]{rgb(230, 92, 48), rgb(40, 140, 203)},
new float[]{0, 1}, Shader.TileMode.CLAMP);
amp.getPaint().setShader(textShader);
}
// invoke it in onCreate on our TextView
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
TextView amp = (TextView)findViewById(R.id.title_amp);
setGradientText(amp);
}
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> // here we'll create two items </selector>
// ...
<item android:state_pressed="true" >
<shape>
<gradient
android:startColor="@color/button_cool"
android:endColor="@color/button_cold"
android:angle="270" />
<stroke
android:width="1dp"
android:color="@color/button_cold" />
<corners
android:radius="3dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
// ...
// ...
<item>
<shape>
<gradient
android:startColor="@color/button_warm"
android:endColor="@color/button_hot"
android:angle="270" />
<stroke
android:width="1dp"
android:color="@color/button_hot" />
<corners
android:radius="3dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
// ...
// ... <color name="button_hot">#FFE62025</color> <color name="button_warm">#FFFECB68</color> <color name="button_cool">#FF63A3D8</color> <color name="button_cold">#FF0059A7</color>
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent){
if(requestCode != REQUEST_ENABLE_BT) {
super.onActivityResult(requestCode, resultCode, intent);
}
if(resultCode != RESULT_OK) {
finish();
} else {
Context context = getApplicationContext();
Toast.makeText(context,"Bluetooth successfuly enabled", Toast.LENGTH_SHORT);
}
}
}