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); } } }