Pages

Android controlling EditText input

Description: coming soon..


  • imeOptions flagNoFullscreen
  • imeOptions actionDone
  • inputType textUri



        <EditText
            android:key="server_ip"
            android:title="server_ip"
            android:dialogTitle="@string/server_ip"
            android:contentDescription="Server IP"
            android:defaultValue="192.168.1.2"
            android:lines="1"
            android:imeOptions="flagNoFullscreen|actionDone"
            android:inputType="textUri"/>


Android: using AIDL

DEFINING AIDL OBJECTS


Let's say we want to define AIDL that we will use to communicate UI Fragment with AndroidService.

/** calls UI to Service **/
MyServiceAIDL.aidl

interface CommandServiceAIDL {
void setSomething(double value);
}



/** callbacks service to UI **/
MyListenerAIDL.aidl

interface MyListenerAIDL {
void onSomeEvent( boolean isOn);
}



ONCE YOU HAVE YOUR AIDL DEFINED



Let's say we have a UI Fragment

import android.content.ServiceConnection;

public class MyFragment extends Fragment implements ServiceConnection {
// ...
 @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
    // we have to BIND this UI to AIDL service
      getActivity().getApplicationContext().bindService(new Intent(CommandConst.ACTION_BIND_SERVICE), this, Context.BIND_AUTO_CREATE);
   }


// ...
@Override
   public void onDestroy() {
      super.onDestroy();
     // WE have to UNBIND this UI form AIDL service
      getActivity().getApplicationContext().unbindService(this);
   }



// class that has all my AIDL methods
private MyServiceAIDL myServiceAIDL;

@Override
   public void onServiceConnected(ComponentName name, IBinder service) {
   
      myServiceAIDL = MyServiceAIDL.Stub.asInterface(service);
      try {
         // register this fragment as a client of the AIDL service
         myServiceAIDL.register(TAG, myServiceAIDL Callback);
         myServiceAIDL.setSomething(123.5);
      }
      catch (RemoteException e) {
         Log.e(TAG, e.getMessage(), e);
      }
   }


private void doSomething(double value) {
         try {
            myServiceAIDL.setSomething(value);
         }
         catch (RemoteException e) {
            e.printStackTrace();
         }
      }


 // CALLBACKS

 private final MyListenerAIDL.Stub commandServiceCallback = new SimpleCommandServiceListener() {

     @Override
      public void onSomeEvent(final boolean isOn) throws RemoteException {
         handler.post(new Runnable() {
            @Override
            public void run() {
               if (someSwitch.isChecked() != isOn) {
                  someSwitch.setChecked(isOn);
               }
            }
         });
      }
}


Android app deploying and monetizing

In this tutorial we will discuss what you need to do to deploy your app to the Google Play Market and how to make money $$$.


Google Play Account


To start deploying apps you will need an account with play.google.com/apps/publish/ . This will cost you one time about $25 fee. 


Uploading new app:

Creating New Key Store

  • create one Key Store per application in case you want to sell or transfer it later
  • use separate passwords for New Key Store and the app-specific Key inside





Once you created a Key Store you can "Generate Signed APK..."





Android Studio can remember your passwords, but you have to set a master password for that.



Upload signed APK to Google Play Market






Once you deploy your app you will be able to monitor various things such as:
  • installs / uninstalls
  • upgrades




Marketing, marketing, and marketing

Marketing is essential for success of your app, it often takes more time and money to market the app than to write the code. This is where we often fail as developers.
  • marketing of a bad product is a waste of money
  • not marketing of a good product is sad
  • build in social sharing features
  • issues of selling and marketing with children apps


Monitoring Tools: Flurry



Monetizing your apps

  • develop apps on topic that is useful to YOU, not others -- You will need the fuel for burning that midnight oil, lots of it!
  • develop multiple apps -- you can never say which "floppy birds" will be successful
  • monitor statistics and adjust accordingly
  • deploy updates often (fix bugs, improve UX)
  • use freemium model unless you are selling an established product that everyone needs
  • in-app purchases (pic below)
  • advertisement model (pic below)
  • donation model -- people too busy to donate
  • free app - make money on your core business (consulting, etc.)


In app purchases: you can specify multiple products



In app purchases "trickle" revenue.





AdMob example of "trickle" revenue.








Do I need Apple iOS version for each of my Android apps?


No, I have developed many, many Android and iOS pairs of apps and I learned one thing:

"If you did not make enough money on your Android app to pay for Apple iOS version development and marketing costs (say.. a million $$$), then keep on developing Android apps until you do; and don't listen to iPhone owners who really want it, it is just not worth it."




Android Studio: migrating to 1.0

Exciting moment -- Android Studio is not a baby anymore!
However, the rite of passage did not come without the growing pains.




The application icon: what happened? We went quite cute and beloved robot (left) to what may be described as an adolescent imitation of Freemasons symbol (center).. some kind of grandiose undercurrents? Not cool.



OK, so the icon is PLAIN UGLY, I guess there is a trend there and everyone is jumping on this bandwagon, give it few years, we will come back to pretty candy-like icons.

Migrating the projects... 

it is really a shame that a project created with Android Studio a 3 days ago cannot be run today, not a big deal to me, but my students who are new to computer science will be thrown off.

Actually, I gave up on trying to UPDATE my project and just created new one, then copied my modules over to it. Not cool, but it worked just fine.

Please note the following:

1) in your main project (e.g. CIT299) build.gradle

    dependencies {
        classpath 'com.android.tools.build:gradle:1.0.0'


2) in ALL your modules build.gradle REPLACE runProguard false with minifyEnabled true

   buildTypes {
        release {
            runProguard false           
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }
    }


    buildTypes {
        release {
            apply plugin: 'maven'
            apply plugin: 'eclipse'
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }




After that I was able to run everything, so it is not a huge deal.

Now, I can sit and watch the Memory Monitor for Facebook eating all my memory :)




If you like this post, please give me your 2 cents ($0.02 litterally) to show token of appreciation and encourage me to write more:

Donate Bitcoins



Mac OS X: cpp tools

In this tutorial I will summarize how to install all tools needed to compile C++ code.

Install Apple's Command Line Tools:

$ xcode-select --install

xcode-select: error: command line tools are already installed, use "Software Update" to install updates






SMS bid app



README.md

*************************************************
APP OVERVIEW
*************************************************

Use cases:
1) User creates offline list of items that will be used in an auction, example:
- sofa, pale blue, heavily used #sofa001
- mountain bike, barely used (I am developer not a sportsman) #bike002
- HTC ONE, Android phone, still working, AT&T #phone003
2) User distributes the list of bid items to FFF (family, friends and fools) by mailing, posting, etc.
3) User enters the bid items into the app
4) User enters the bid deadline per item into the app
6) User enters a minimum bid price per item into the app
7) FFF send SMS messages with bid in format:

bid #bike002 $30

8) The app tally up the highest bid and replies to FFF

- Minimum bid for item #bike002 is $30
- These is a higher bid for #bike002 in amount of $31

9) User can monitor the winning bids in the app

#sofa001
No bids

#bike002 minimum bid $30
- $31 from 6508151234
- $30 from 6508151432

10) there are many additional features you can add to this application


Step 1) Copy appSMS


We will use the previous app we wrote as a starting point.
Synch the class GIT repo:
- git fetch
- git add -all
- git commit -m "my previous changes xyz"
- git rebase
- git push
You can copy the version committed by the instructor to your desktop and then "Import Module" in AndroidStudio
Name it appSMSBid

Step 2) Rename the app in res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">"SMS Auction"</string>
    <string name="action_settings">Settings</string>
</resources>


Step 3) Change the package name (app unique identifier)


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.chicagoandroid.android.app.sms.bid">

Step 4) Move classes


  • Create new package bid
  • Move classes to package com.chicagoandroid.android.app.sms.bid

Step 5) Run the app


adb shell 'pm list packages -f' | grep /data/app/com.chicagoandroid.android.app.sms

Step 6) see what class apps you have running


$ adb shell 'pm list packages -f' | grep /data/app/com.chicagoandroid.android.app

package:/data/app/com.chicagoandroid.android.app.sms-1.apk=com.chicagoandroid.android.app.smspackage:/data/app/com.chicagoandroid.android.app.json.weather-1.apk=com.chicagoandroid.android.app.json.weather

Step 7) Create a new Java class BidItem.java





  •  private String description;
  •  private String itemID;
  •  private double minBidPrice;


  • Generate Getters and Setters methods




    Add constructor:

       BidItem(String itemID, double minBidPrice, String description) {
          this.setDescription(description);
          this.setItemID(itemID);
          this.setMinBidPrice(minBidPrice);
       }


    Override default toString method:


       @Override
       public String toString() {
          StringBuffer output = new StringBuffer(150);
          output.append("itemID: " + getItemID() + ", ");
          output.append("description: " + getDescription() + ", ");
          output.append("minBidPrice: $" + getMinBidPrice());
          return output.toString();
       }





    Step 8: Create Java class Bid

    constructor:

       Bid(String bidID, double bidAmount, String personNumber) {
          this.setBidID(bidID);
          this.setBidAmount(bidAmount);
          this.setPersonNumber(personNumber);
       }







    Step 9: Create class BidEngine


    package com.chicagoandroid.android.app.sms.bid;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    /**
     * Created by uki on 12/6/14.
     */
    public class BidEngine {
       /** A collection of BidItems indexed by itemID **/
       private Map<String, BidItem> bidItems;
       private List<Bid> bids;
       BidEngine() {
          createMockBidItems();
          createMockBids();
       }
       //TODO public void addItem(BidItem)
       //TODO public void removeItem(BidItem)
       //TODO public void changeItem(BidItem);
       public Map getBidItems() {
          return this.bidItems;
       }
       private void createMockBidItems() {
          bidItems = new HashMap<String, BidItem>();
          BidItem sofa = new BidItem("sofa001", 15.00, "sofa, pale blue, heavily used");
          bidItems.put(sofa.getItemID(), sofa);
          BidItem bike = new BidItem("bike002", 50.00, "mountain bike, barely used (I am developer not a sportsman)");
          bidItems.put(bike.getItemID(), bike);
          BidItem phone = new BidItem("phone003", 30.00, "HTC ONE, Android phone, still working, AT&T");
          bidItems.put(phone.getItemID(), phone);
       }
       private void createMockBids() {
          bids = new ArrayList<Bid>();
          Bid sofa1 = new Bid("sofa001", 15.00, "6508151234");
          bids.add(sofa1);
          Bid sofa2 = new Bid("sofa001", 16.00, "6508151232");
          bids.add(sofa2);
          Bid sofa3 = new Bid("sofa001", 17.00, "6508151234");
          bids.add(sofa3);
       }
       public String printMyBidItems() {
          StringBuffer output = new StringBuffer();
          for (BidItem item : bidItems.values()) {
             output.append(item.toString() + "\n");
          }
          return output.toString();
       }
    }




    Step 10: Create initial UI









    AndroidStudio: install *.md plugin

    It is a common practice to provide a README text file in the root of the project.
    The file usually specify:
    - project overview
    - project dependencies
    - how to build the project
    - copyrights

    It is became the standard to write this file in a MarkDown format README.md

    Once you create a new *.md text file the AndroidStudio tells you that it does not recognize the format and to download the plugin.



    Android: checking IP of your devie





    $ adb shell netcfg
    lo       UP                                   127.0.0.1/8   0x00000049 00:00:00:00:00:00
    can0     UP                                     0.0.0.0/0   0x000000c1 00:00:00:00:00:00
    can1     UP                                     0.0.0.0/0   0x000000c1 00:00:00:00:00:00
    can2     UP                                     0.0.0.0/0   0x000000c1 00:00:00:00:00:00
    can3     UP                                     0.0.0.0/0   0x000000c1 00:00:00:00:00:00
    tunl0    DOWN                                   0.0.0.0/0   0x00000080 00:00:00:00:00:00
    sit0     DOWN                                   0.0.0.0/0   0x00000080 00:00:00:00:00:00
    ip6tnl0  DOWN                                   0.0.0.0/0   0x00000080 00:00:00:00:00:00

    eth0     UP                                 192.168.1.4/24  0x00001043 00:04:a3:92:d4:0b

    If you like this post, please give me your 2 cents ($0.02 litterally) to show token of appreciation and encourage me to write more:

    Donate Bitcoins



    Android: start app on reboot

    In this tutorial we will learn how to start an app (Activity or Service) on Android reboot.



    Step: Android manifest

            <receiver
                    android:name=".BootBroadcastReceiver"
                    android:enabled="true"
                    android:exported="true"
                    android:label="Start SMS on boot">
                <intent-filter>
                    <action android:name="android.intent.action.BOOT_COMPLETED"/>
                </intent-filter>
            </receiver>

        </application>

        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    </manifest>



    Step: BroadcastReceiver

    package com.chicagoandroid.android.app.sms;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    /**
     * Created by uki on 11/22/14.
     */
    public class BootBroadcastReceiver extends BroadcastReceiver {
       @Override
       public void onReceive(Context context, Intent intent) {
          if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
             Intent serviceIntent = new Intent(context, MainActivity.class);
             context.startService(serviceIntent);
          }
       }
    }



    Android: detect your own phone number

    In this tutorial you will learn how to detect your own phone number programmatically



    Step: add permission to Android Manifest

        <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    </manifest>



    Step: use TelephonyManager to read the number

       private String getMyPhoneNumber() {
          TelephonyManager manager = (TelephonyManager) appContext.getSystemService(Context.TELEPHONY_SERVICE);
          String myPhoneNumber = manager.getLine1Number();
          return myPhoneNumber;
       }






    Android: send SMS programmatically

    This tutorial is a continuation of previous post:
    http://ukitech.blogspot.com/2014/11/android-calling-activity-from.html

    We will learn how to SENT a SMS programmatically.


    Step: Improve UI





    <LinearLayout 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"
                  android:orientation="vertical"
                  tools:context=".MainActivity">
        <TextView
                android:text="If SMS starts with.. "
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        <EditText
                android:id="@+id/sms_search_text"
                android:layout_marginLeft="32dp"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:hint="CIT299?"/>

        <TextView
                android:text="Respond with the SMS message.."
                android:layout_marginTop="16dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        <EditText
                android:id="@+id/sms_respond_text"
                android:layout_marginLeft="32dp"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:hint="finish this app, study hard, prepare the final"/>

        <TextView
                android:text="Received SMS.."
                android:layout_marginTop="16dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        <ScrollView
                android:layout_width="fill_parent"
                android:layout_height="100dp">
            <TextView
                    android:id="@+id/sms_received"
                    android:layout_marginLeft="32dp"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:hint="1\n2\n3\n4\n5\n6\n7\n8\n9\n"/>
        </ScrollView>
    </LinearLayout>










    Step: new class SmsSender.java


    package com.chicagoandroid.android.app.sms;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.telephony.PhoneNumberUtils;
    import android.telephony.SmsManager;
    import android.telephony.TelephonyManager;
    import android.util.Log;
    import android.widget.Toast;
    /**
     * Created by uki on 11/22/14.
     */
    public class SmsSender {
       private static final String TAG = SmsSender.class.getSimpleName();
       Context appContext;
       public SmsSender(Context context) {
          appContext = context;
       }
       public void send(String destinationAddress, String text) {
          if (!PhoneNumberUtils.isWellFormedSmsAddress(destinationAddress)) {
             Log.e(TAG, "SMS number is malformed");
             return;
          }
          SmsManager smsManager = SmsManager.getDefault();
          String scAddress = null; //getMyPhoneNumber();
          PendingIntent sentIntent = null;
          PendingIntent deliveryIntent = null; // broadcast when delivered
          try {
             smsManager.sendTextMessage(destinationAddress, scAddress, text, sentIntent, deliveryIntent);
             Log.w(TAG, "message sent: " + text + " to " + destinationAddress);
          }
          catch (Exception e) {
             Log.e(TAG, "SMS not sent: " + e.getMessage());
             //TODO handle
          }
       }




    Step: Send message from SmsParser

       public void processReceivedSms(String smsOriginatingAddress, String smsDisplayMessage) {
          final String tag = TAG + ".processReceivedSms";
          Log.i(tag, "SMS from  " + smsOriginatingAddress);
          Log.i(tag, "SMS body  " + smsDisplayMessage);
          mainActivity.appendSmsToUI("SMS from  " + smsOriginatingAddress + "\n" + smsDisplayMessage);
          String keyword = mainActivity.getSmsTextSearch();
          if (doesSmsStartWith(smsDisplayMessage, keyword)) {
             Log.i(tag, "SMS does start with " + keyword);
             String responseText = mainActivity.getSmsRespondSearch();
             Log.i(tag, "Attempting to respond: " + responseText);
             SmsSender sender = new SmsSender(appContext);
             sender.send(smsOriginatingAddress, responseText);

          }
          else
             Log.e(TAG + ".onReceive", "SMS does not start with " + keyword);
       }



    Step: MainActivity - full listing

    package com.chicagoandroid.android.app.sms;
    import android.support.v7.app.ActionBarActivity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.widget.EditText;
    import android.widget.TextView;
    public class MainActivity extends ActionBarActivity {
       private EditText etSmsTextSearch, etSmsRespondSearch;
       private TextView smsReceived;
       @Override
       protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          etSmsTextSearch = (EditText) findViewById(R.id.sms_search_text);
          etSmsRespondSearch = (EditText) findViewById(R.id.sms_respond_text);
          smsReceived = (TextView) findViewById(R.id.sms_received);
          // keep reference to Activity context
          MyApplication myApplication = (MyApplication) this.getApplicationContext();
          myApplication.mainActivity = this;
       }
       public String getSmsTextSearch() {
          String text = etSmsTextSearch.getText().toString();
          //todo save to preferences
          if (text == null || text.equalsIgnoreCase("")) text = "Not Provided";
          return text;
       }
       public String getSmsRespondSearch() {
          String text = etSmsRespondSearch.getText().toString();
          //todo save to preferences
          if (text == null || text.equalsIgnoreCase("")) text = "Not Provided";
          return text;
       }
       public void appendSmsToUI(String text) {
          smsReceived.append(text + "\n");
       }
       @Override
       public boolean onCreateOptionsMenu(Menu menu) {
          // Inflate the menu; this adds items to the action bar if it is present.
          getMenuInflater().inflate(R.menu.main, menu);
          return true;
       }
       @Override
       public boolean onOptionsItemSelected(MenuItem item) {
          // Handle action bar item clicks here. The action bar will
          // automatically handle clicks on the Home/Up button, so long
          // as you specify a parent activity in AndroidManifest.xml.
          int id = item.getItemId();
          if (id == R.id.action_settings) {
             return true;
          }
          return super.onOptionsItemSelected(item);
       }
    }




    Android: calling Activity from BroadcastReceiver

    This tutorial is a continuation of the previous post:
    http://ukitech.blogspot.com/2014/11/android-sms-app.html

    We will learn how to connect Activity (UI) and BroadcastReceiver which does not know the Activity (and hence the UI) using Application class.



    Step: create Application class

    package com.chicagoandroid.android.app.sms;
    import android.app.Application;
    /**
     * Created by uki on 11/22/14.
     */
    public class MyApplication extends Application {
       @Override
       public void onCreate() {
          super.onCreate();
       }
       /**
        * we use this class member to hold instance of our activity.
        */
       public MainActivity mainActivity;
    }


    Step: Declare it in the AndroidManifest.xml file

        <application
                android:name="com.chicagoandroid.android.app.sms.MyApplication"

    Step: Pass Activity to Application

    public class MainActivity extends ActionBarActivity {
    ...
       @Override
       protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          etSmsTextSearch = (EditText) findViewById(R.id.sms_search_text);
          etSmsRespondSearch = (EditText) findViewById(R.id.sms_respond_text);
          smsReceived = (TextView) findViewById(R.id.sms_received);
          // keep reference to Activity context
          MyApplication myApplication = (MyApplication) this.getApplicationContext();
          myApplication.mainActivity = this;

       }


    Step: In your BroadcastReceiver make sure you use Application Context

       @Override
       public void onReceive(Context context, Intent intent) {
          ...
             parser = new SmsParser(context);


    Step: From Application Context get the Activity

       public SmsParser(Context context) {
          mainActivity = ((MyApplication) context.getApplicationContext()).mainActivity;
       }



    Step: Form Activity you can call PUBLIC methods.

          String keyword = mainActivity.getSmsTextSearch();



    Android Studio: GIT: .gitignore

    Managing which files should be ignored by GIT (code repository) it an important skill. It is done by .gitignore file in which each line represents a directory, a pattern, or a file to be ignored.


    Step: Stay organized


    Keep your GIT ignore file organized, I keep it ordered alphabetically using a script in TextWrangler editor.

    Step: Find Plugin

    Always investigate plugins available to support your code.





    Android: SMS app: receiving

    In this tutorial we will learn how to handle incoming messages, how to parse them to find a pattern, and how to send an SMS.

    Step: As always update to newest version of your IDE and tools (SDK manager)


    Step: Create a new Android app module.




    Step: Select cool icon for your SMS app



    Step: Select Blank Activity


    Step: Name your MainActivity




    Step: Add PERMISSIONS

    Step: Add receiver class declaration

    Step: Add intent-filter for SMS_RECEIVED


    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.chicagoandroid.android.app.sms">
        <application
                android:allowBackup="true"
                android:icon="@drawable/ic_launcher"
                android:label="@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>
            <receiver android:name=".SmsReceiver">
                <intent-filter>
                    <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
                </intent-filter>
            </receiver>


        </application>

        <uses-permission android:name="android.permission.RECEIVE_SMS"/>
        <uses-permission android:name="android.permission.SEND_SMS"/>

    </manifest>

    Step: Create a new class SmsReceiver

    package com.chicagoandroid.android.app.sms;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Build;
    import android.os.Bundle;
    import android.provider.Telephony;
    import android.telephony.SmsMessage;
    import android.util.Log;
    //deprecated import android.telephony.gsm.SmsMessage;
    /**
     * Created by uki on 11/22/14.
     */
    public class SmsReceiver extends BroadcastReceiver {
          private static final String TAG = SmsReceiver.class.getSimpleName();
          private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
          @Override
          public void onReceive(Context context, Intent intent) {
                final String tag = TAG + ".onReceive";
                Bundle bundle = intent.getExtras();
                if (bundle == null) {
                      Log.w(tag, "BroadcastReceiver failed, no intent data to process.");
                      return;
                }
                if (intent.getAction().equals(SMS_RECEIVED)) {
                      Log.d(tag, "SMS_RECEIVED");
                      String smsOriginatingAddress, smsDisplayMessage;
                      /**
                       * You have to CHOOSE which code snippet to use NEW (KitKat+), or legacy
                       * Please comment out the for{} you don't want to use.
                       */
                      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                            Log.d(tag, "KitKat or newer + API " + Build.VERSION.SDK_INT);
                            // API level 19 (KitKat 4.4) getMessagesFromIntent
                            for (SmsMessage message : Telephony.Sms.Intents.getMessagesFromIntent(intent)) {
                                  if (message == null) {
                                        Log.e(tag, "SMS message is null -- ABORT");
                                        break;
                                  }
                                  smsOriginatingAddress = message.getDisplayOriginatingAddress();
                                  smsDisplayMessage = message.getDisplayMessageBody(); //see getMessageBody();
                                  SmsParser.processReceivedSms(smsOriginatingAddress, smsDisplayMessage);
                            }
                      }
                      else { // BELOW KITKAT
                            Log.d(tag, "legacy SMS implementation (before KitKat) API " + Build.VERSION.SDK_INT);
                            // Processing SMS messages the OLD way, before KitKat, this WILL work on KitKat or newer Android
                            // PDU is a “protocol data unit”, which is the industry format for an SMS message
                            Object[] data = (Object[]) bundle.get("pdus");
                            for (Object pdu : data) {
                                  SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu);
                                  if (message == null) {
                                        Log.e(tag, "SMS message is null -- ABORT");
                                        break;
                                  }
                                  smsOriginatingAddress = message.getDisplayOriginatingAddress();
                                  smsDisplayMessage = message.getDisplayMessageBody(); // see getMessageBody();
                                  SmsParser.processReceivedSms(smsOriginatingAddress, smsDisplayMessage);
                            }
                      }
                } // onReceive method
          }
    }





    StockOverflow: http://stackoverflow.com/a/27076215/3566154

    Step: Simple SMS parser

    package com.chicagoandroid.android.app.sms;

    import android.util.Log;

    /**
     * Created by uki on 11/22/14.
     */
    public class SmsParser {
       private static final String TAG = SmsParser.class.getSimpleName();
       public static void processReceivedSms(String smsOriginatingAddress, String smsDisplayMessage) {
          final String tag = TAG + ".processReceivedSms";
          Log.i(tag, "SMS from  " + smsOriginatingAddress);
          Log.i(tag, "SMS body  " + smsDisplayMessage);
          String keyword = "Info?";
          if (doesSmsStartWith(smsDisplayMessage, keyword)) {
             Log.i(tag, "SMS does start with " + keyword);
          }
          else
             Log.e(TAG + ".onReceive", "SMS does not start with " + keyword);
       }
       /**
        * this is a very simple method to detect if
        * the SMS message starts with a given character sequence
        */
       private static boolean doesSmsStartWith(String smsMessage, String txt) {
          return smsMessage.trim().toLowerCase().startsWith(txt.trim().toLowerCase());
       }
    }


    Step: run this app and test receiving SMS in LogCat


    11-22 04:55:13.783  10317-10317/com.chicagoandroid.android.app.sms D/SmsReceiver.onReceive﹕ SMS_RECEIVED
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms D/SmsReceiver.onReceive﹕ KitKat or newer
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms I/SmsParser.processReceivedSms﹕ SMS from  +1650815xxxx // privacy
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms I/SmsParser.processReceivedSms﹕ SMS body  Test 6
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms E/SmsParser.onReceive﹕ SMS does not start with Info?
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms D/SmsReceiver.onReceive﹕ legacy SMS implementation (before KitKat)
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms I/SmsParser.processReceivedSms﹕ SMS from  +16508156603
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms I/SmsParser.processReceivedSms﹕ SMS body  Test 6
    11-22 04:55:13.803  10317-10317/com.chicagoandroid.android.app.sms E/SmsParser.onReceive﹕ SMS does not start with Info?

    Step: create basic UI






    android-maven-plugin-4.0

    As you are migrating your projects from old to new Android structure you may encounter:




    [ERROR] Found files or folders in non-standard locations in the project! [ERROR] ....This might be a side-effect of a migration to Android Maven Plugin 4+.

    android-maven-plugin-4.0 uses the new Android Studio folder layout which is much more Mavenish.

    • Android resources should be is src\main\res, 
    • Android assets in src\main\assets, 
    • AndroidManifest.xml in src\main


    Mac OS X Yosemite: Java and Android developer

    If you did not upgrade yet, you will probably do so within days, here are my observations so far.




    Java 

    When you upgrade to Yosemite your previous Java is still there:

    $ java -version
    java version "1.8.0_20-ea"
    Java(TM) SE Runtime Environment (build 1.8.0_20-ea-b05)

    Java HotSpot(TM) 64-Bit Server VM (build 25.20-b05, mixed mode)

    but Apple open dialogs that you have to install older Java, I am probably missing a symbolic link from what the default installation should be.

    Java 1.6 from Apple:
    http://support.apple.com/kb/DL1572


    IntelliJ IDEA


    The software says my Android SDK is missing.





    12a. Android: JSON OpenWeatherMap

    In this tutorial we will implement basic weather OpenWeatherMap API using JSON (JavaScript Object Notation).
    links:

    API information:
    http://openweathermap.org/api

    JSON:
    http://api.openweathermap.org/data/2.5/weather?q=Mundelein,IL

    Image URL:
    http://openweathermap.org/img/w/10d.png

    JSON (pretty) formatter in TextWrangler editor (Mac)
    http://ukitech.blogspot.com/2012/08/format-json-in-free-textwrangler.html

    Example JSON data:

    http://api.openweathermap.org/data/2.5/weather?q=Mundelein,IL
    http://openweathermap.org/img/w/10d.png

    {
      "base": "cmc stations",
      "clouds": {
        "all": 1
      },
      "cod": 200,
      "coord": {
        "lat": 42.27,
        "lon": -88
      },
      "dt": 1416040500,
      "id": 4903184,
      "main": {
        "humidity": 92,
        "pressure": 1027,
        "temp": 265.36,
        "temp_max": 268.15,
        "temp_min": 261.15
      },
      "name": "Mundelein",
      "sys": {
        "country": "United States of America",
        "id": 2981,
        "message": 0.0294,
        "sunrise": 1416055401,
        "sunset": 1416090602,
        "type": 1
      },
      "weather": [
        {
          "description": "sky is clear",
          "icon": "01n",
          "id": 800,
          "main": "Clear"
        }
      ],
      "wind": {
        "deg": 274.501,
        "speed": 4.54
      }
    }

    Step: Create a new Android module

    • File > New Module...
    • Android Application
    • Application Name: Weather Station
    • Module Name: appWeatherStationJson
    • Package Name: com.yourname.android.app.json.weather
    • Min SDK: 8
    • Target SDK: 19 (KitKat 4.4)
    • Compile with: 21
    • Java Lang: 6.0
    • Theme: Holo Light
    • Create Activity
    • Suport mode: Fragments
    • Suport mode: Action Bar


    Next
    • Blank Activity
    Next
    • MainActivity
    • activity_main
    Finish


    Step: add INTERNET permission

    We need to add INTERNET permission since we will be accessing JSON over HTTP

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.chicagoandroid.android.app.json.weather">
        <application
                android:allowBackup="true"
                android:icon="@drawable/ic_launcher"
                android:label="@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>
        <uses-permission android:name="android.permission.INTERNET"/>
    </manifest>



    Android: Camera

    In this tutorial we will learn how to capture an image using Android Camera and how to pass this image from a Fragment back to Activity

    Step: create a new Module "appSimpleCamera"

    Step: design layout in "fragment_camera.xml"






























    Step: Add ImageView and Fragment container to activity_main.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:tools="http://schemas.android.com/tools"
                  android:orientation="vertical"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  tools:context=".MainActivity"
                  tools:ignore="MergeRootFrame">
        <ImageView
                android:id="@+id/picturePreviewThumb"
                android:src="@android:drawable/ic_menu_gallery"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:contentDescription="Photo Capture"
                />
        <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
                     android:id="@+id/camera_fragment_container"
                     android:layout_width="match_parent"
                     android:layout_height="match_parent"/>
    </LinearLayout>



    Step: implement Fragment

    package com.chicagoandroid.cit299.simplecamera;
    import android.content.Intent;
    import android.graphics.Bitmap;
    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.support.v4.app.Fragment;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    /**
     * Camera fragment containing a simple view.
     */
    public class CameraFragment extends Fragment {
       ImageView takePhoto, picturePreview, savePhoto, cancel;
       /** picturePreviewThumb belongs to Activity, not this Fragment **/
       ImageView picturePreviewThumb;
       Bitmap capturedPhoto;
       @Override
       public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
          View rootView = inflater.inflate(R.layout.fragment_camera, container, false);
          return rootView;
       }
       /**
        * Add your UI elements and their functionality here,
        * because in onCreateView(...) UI does not exist yet.
        * @param savedInstanceState
        */
       @Override
       public void onActivityCreated(@Nullable Bundle savedInstanceState) {
          super.onActivityCreated(savedInstanceState);
          // Inside fragment you have to use getView()..
          cancel = (ImageView) getView().findViewById(R.id.cancel);
          takePhoto = (ImageView) getView().findViewById(R.id.takePhoto);
          savePhoto = (ImageView) getView().findViewById(R.id.savePhoto);
          savePhoto.setVisibility(View.INVISIBLE);
          picturePreview = (ImageView) getView().findViewById(R.id.picturePreview);
          takePhoto.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                useCameraApp();
             }
          });
          savePhoto.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                passPhotoToActivity();
             }
          });
          cancel.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                clearScreen();
             }
          });
       }
       public void useCameraApp() {
          Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
          startActivityForResult(intent, 0);
       }
       @Override
       public void onActivityResult(int requestCode, int resultCode, Intent data) {
          super.onActivityResult(requestCode, resultCode, data);
          Bundle bundle = data.getExtras();
          /** If user presses BACK BUTTON in Camera the bundle wil be null. **/
          if (bundle != null) {
             capturedPhoto = (Bitmap) bundle.get("data");
             picturePreview.setImageBitmap(capturedPhoto);
             savePhoto.setVisibility(View.VISIBLE);
          }
       }
       private void passPhotoToActivity() {
          picturePreviewThumb = (ImageView) getActivity().findViewById(R.id.picturePreviewThumb);
          picturePreviewThumb.setImageBitmap(capturedPhoto);
       }
       private void clearScreen() {
          /** Same as android:src="@android:drawable/ic_menu_gallery" **/
          picturePreview.setImageResource(android.R.drawable.ic_menu_gallery);
          savePhoto.setVisibility(View.INVISIBLE);
       }
    }



    Step: implement MainActivity


    package com.chicagoandroid.cit299.simplecamera;
    import android.support.v7.app.ActionBarActivity;
    import android.support.v4.app.Fragment;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.ViewGroup;
    public class MainActivity extends ActionBarActivity {
       @Override
       protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          addCameraFragment(savedInstanceState);
       }
       @Override
       public boolean onCreateOptionsMenu(Menu menu) {
          // Inflate the menu; this adds items to the action bar if it is present.
          getMenuInflater().inflate(R.menu.main, menu);
          return true;
       }
       @Override
       public boolean onOptionsItemSelected(MenuItem item) {
          // Handle action bar item clicks here. The action bar will
          // automatically handle clicks on the Home/Up button, so long
          // as you specify a parent activity in AndroidManifest.xml.
          int id = item.getItemId();
          if (id == R.id.action_settings) {
             return true;
          }
          return super.onOptionsItemSelected(item);
       }
       /**
        * This method will be called in onCreate()
        * @param bundle savedInstanceState
        */
       private void addCameraFragment(Bundle bundle) {
          int containerId = R.id.camera_fragment_container;
          /**
           * Verify that your activity layout include a fragment container.
           * Depending on layout (size, orientation) and logic the container may not be present.
           */
          if (findViewById(containerId) == null) {
             return;
          }
          /** If restoring Activity do nothing to prevent creating existing Fragment. **/
          if (bundle != null) {
             return;
          }
          CameraFragment fragment = new CameraFragment();
          /** Pass in calling Intent's values, setting the Bundle in constructor is not recommended  **/
          // Bundle bundle = getIntent().getExtras(); //another way to get the bundle
          fragment.setArguments(bundle);
          getSupportFragmentManager().beginTransaction().add(containerId, fragment).commit();
       }
    }






    Android: MediaPlayer

    In this tutorial we will learn how to play MP3 file saved in the application. We will create an app that plays a soothing sound of the forest (or waves, or rain) that plays in the loop. We will also learn how to control the playback.


    Step 1: Create a new Module "appNatureSounds"

    • min API 8
    Step 2: Design UI of your player




    <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:paddingBottom="@dimen/activity_vertical_margin"
                    android:paddingLeft="@dimen/activity_horizontal_margin"
                    android:paddingRight="@dimen/activity_horizontal_margin"
                    android:paddingTop="@dimen/activity_vertical_margin"
                    tools:context=".MainActivity">
        <TextView
                android:id="@+id/uiSoundTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Song Title"/>
        <LinearLayout
                android:id="@+id/controlLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_alignParentBottom="true">
            <ImageButton
                    android:id="@+id/uiFastForward"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="20"
                    android:layout_marginBottom="14dp"
                    android:onClick="forward"
                    android:src="@android:drawable/ic_media_ff"/>
            <ImageButton
                    android:layout_weight="40"
                    android:id="@+id/uiPlayButton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="24dp"
                    android:onClick="play"
                    android:src="@android:drawable/ic_media_play"/>
            <ImageButton
                    android:layout_weight="20"
                    android:id="@+id/uiPausePlay"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="14dp"
                    android:onClick="pause"
                    android:src="@android:drawable/ic_media_pause"/>
            <ImageButton
                    android:layout_weight="20"
                    android:id="@+id/uiRewind"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_marginLeft="22dp"
                    android:onClick="rewind"
                    android:src="@android:drawable/ic_media_rew"/>
        </LinearLayout>
        <LinearLayout
                android:id="@+id/progressLayout"
                android:layout_above="@id/controlLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
            <TextView
                    android:id="@+id/uiCurrentTime"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="15"
                    android:text="@string/zero_time"
                    android:textAppearance="?android:attr/textAppearanceSmall"/>
            <SeekBar
                    android:id="@+id/uiProgressSeekBar"
                    android:layout_weight="70"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
            <TextView
                    android:id="@+id/uiDuration"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="right"
                    android:layout_gravity="right"
                    android:layout_weight="15"
                    android:text="@string/zero_time"
                    android:textAppearance="?android:attr/textAppearanceSmall"/>
        </LinearLayout>
        <ImageView
                android:id="@+id/uiPlayerBackground"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_above="@id/progressLayout"
                android:layout_below="@+id/uiSoundTitle"
                android:src="@drawable/bg_rainforest"/>
    </RelativeLayout>



    Step:  Add any language specific text




    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">Nature Sounds</string>
        <string name="action_settings">Settings</string>
        <string name="zero_time">0:0</string>
    </resources>


    Step: Record, or find a SHORT royalty-free MP3 you want to play





    Step: create a class that will hold the TIME of the song.



    package com.chicagoandroid.cit299.naturesounds.appnaturesounds;
    import java.text.SimpleDateFormat;
    /**
     * Wrapper for time mm:ss
     * Created by uki on 11/8/14.
     */
    public class PlayTime {
       long time;
       public PlayTime(long value) {
          time = value;
       }
       public void update(int value) {
          time = (long) value;
       }
       public void add(int value) {
          time = time + value;
       }
       public boolean subtract(int value) {
          if (time - value > 0) {
             time = time - value;
             return true;
          }
          return false;
       }
       public PlayTime(int value) {
          time = (long) value;
       }
       public int toInt() {
          return (int) time;
       }
       public String toString() {
          SimpleDateFormat ft = new SimpleDateFormat("mm:ss");
          return ft.format(time);
       }
    }





    Step: MainActivity.OnCreate()



    package com.chicagoandroid.cit299.naturesounds.appnaturesounds;
    import android.media.MediaPlayer;
    import android.os.Handler;
    import android.support.v7.app.ActionBarActivity;
    import android.os.Bundle;
    import android.view.Gravity;
    import android.view.Menu;
    import android.view.View;
    import android.widget.*;
    /** Nature Sounds app */
    public class MainActivity extends ActionBarActivity {
       private static int FORWARD_TIME = 5000;
       private static int REWIND_TIME = 5000;
       private static int DELAY_TIME = 100; // update 10 times per second
       private TextView soundTitle, currentTimeLabel, durationTimeLabel;
       private ImageView backgroundImage;
       private MediaPlayer mediaPlayer;
       private PlayTime currentTime = new PlayTime(0);
       private PlayTime durationTime = new PlayTime(0);
       private Handler threadHandler = new Handler();;
       private SeekBar progressSeekBar;
       private ImageButton playButton, pauseButton;
       private boolean userChangingProgress = false;
       @Override
       protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          soundTitle = (TextView) findViewById(R.id.uiSoundTitle);
          currentTimeLabel = (TextView) findViewById(R.id.uiCurrentTime);
          durationTimeLabel = (TextView) findViewById(R.id.uiDuration);
          progressSeekBar = (SeekBar) findViewById(R.id.uiProgressSeekBar);
          playButton = (ImageButton) findViewById(R.id.uiPlayButton);
          pauseButton = (ImageButton) findViewById(R.id.uiPausePlay);
          backgroundImage = (ImageView) findViewById(R.id.uiPlayerBackground);
          // set UI values to initial state before play
          durationTimeLabel.setText(durationTime.toString());
          currentTimeLabel.setText(currentTime.toString());
          progressSeekBar.setClickable(false);
          pauseButton.setEnabled(false);
          // this could be loaded from the database
          soundTitle.setText("Rain forest");
          mediaPlayer = MediaPlayer.create(this, R.raw.sound_001);
          backgroundImage.setImageResource(R.drawable.bg_rainforest);
       }


    Step: play method


       public void play(View view) {
          //play sound over and over
          mediaPlayer.setLooping(true);
          mediaPlayer.start();
          toast("Playing sound");
          durationTime.update(mediaPlayer.getDuration());
          currentTime.update(mediaPlayer.getCurrentPosition());
          durationTimeLabel.setText(durationTime.toString());
          currentTimeLabel.setText(currentTime.toString());
          progressSeekBar.setProgress(currentTime.toInt());
          progressSeekBar.setMax(durationTime.toInt());
          pauseButton.setEnabled(true);
          playButton.setEnabled(false);
          threadHandler.postDelayed(SongUpdateThread, DELAY_TIME);
          progressSeekBar.setOnSeekBarChangeListener(new SeekBarListener());
       }




    Step: create  SeekBarListener





       class SeekBarListener implements OnSeekBarChangeListener {
          /**
           * Notification that the progress level has changed. Clients can use the fromUser parameter
           * to distinguish user-initiated changes from those that occurred programmatically.
           *
           * @param seekBar The SeekBar whose progress has changed
           * @param progress The current progress level. This will be in the range 0..max where max
           * was set by {@link android.widget.ProgressBar#setMax(int)}. (The default value for max is 100.)
           * @param fromUser True if the progress change was initiated by the user.
           */
          @Override
          public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
             if (fromUser) {
                currentTime.update(progress);
                mediaPlayer.seekTo(currentTime.toInt());
             }
          }
          /**
           * Notification that the user has started a touch gesture. Clients may want to use this
           * to disable advancing the seekbar.
           *
           * @param seekBar The SeekBar in which the touch gesture began
           */
          @Override
          public void onStartTrackingTouch(SeekBar seekBar) {
             userChangingProgress = true;
          }
          /**
           * Notification that the user has finished a touch gesture. Clients may want to use this
           * to re-enable advancing the seekbar.
           *
           * @param seekBar The SeekBar in which the touch gesture began
           */
          @Override
          public void onStopTrackingTouch(SeekBar seekBar) {
             userChangingProgress = false;
          }
       }


    Step: additional control methods




       public void pause(View view) {
          toast("Pausing sound");
          mediaPlayer.pause();
          pauseButton.setEnabled(false);
          playButton.setEnabled(true);
       }
       public void forward(View view) {
          int temp = currentTime.toInt();
          if ((temp + FORWARD_TIME) <= durationTime.toInt()) {
             currentTime.add(FORWARD_TIME);
             mediaPlayer.seekTo(currentTime.toInt());
          }
          else {
             toast("Cannot jump forward " + FORWARD_TIME / 1000 + " seconds");
          }
       }
       public void rewind(View view) {
          if ((currentTime.toInt() - REWIND_TIME) > 0) {
             currentTime.subtract(REWIND_TIME);
             mediaPlayer.seekTo(currentTime.toInt());
          }
          else {
             toast("Cannot jump backward " + REWIND_TIME / 1000 + " seconds");
          }
       }



    Step: utility methods


     void toast(String text) {
          Toast toast = Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG);
          toast.setGravity(Gravity.CENTER | Gravity.CENTER, 0, 0);
          toast.show();
       }
       @Override
       public boolean onCreateOptionsMenu(Menu menu) {
          getMenuInflater().inflate(R.menu.main, menu);
          return true;
       }


    Step: Song Update Thread


       private Runnable SongUpdateThread = new Runnable() {
          public void run() {
             if (!userChangingProgress) {
                currentTime.update(mediaPlayer.getCurrentPosition());
                progressSeekBar.setProgress(currentTime.toInt());
             }
             currentTimeLabel.setText(currentTime.toString());
             // call itself
             threadHandler.postDelayed(this, DELAY_TIME);
          }
       };



    res/dimens.xml


       
        16dp
        16dp