Build an Async Restful Webservice Client in Android in 5min and read BA flight data

In an earlier blog post we build a Restful WS running on Wildfly, now lets build the client part for Android. Thanks to the okHttp, an Apache 2.0 licensed java and Android library, this becomes a very easy challenge. In a mobile application we definitely want to implement a async call as we cannot rely on the response time being fast and a blocked application is not a pleasant user experience.

To make it a bit more interesting for the aviation context of my blog, we will take a real airline webservice and show operational flight data  in the second step.

Step 1: The Basic Webservice Client

Lets get started, I skip the project creation steps, you can create any basic empty Android application.

Add the dependency

..
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.1.1'
    testCompile 'junit:junit:4.12'
compile 'com.squareup.okhttp3:okhttp:3.9.0'
}

Implement the WS call with a call back

(we use the serverside we implemented previously with 2 parameters)

    private void testOKHttp() {

        OkHttpClient httpClient = new OkHttpClient();
        String callURL = "http://www.yourservername.com/DemoRandomizer/randomservice/random/numberrange";

        HttpUrl.Builder urlBuilder = HttpUrl.parse(callURL).newBuilder();
        urlBuilder.addQueryParameter("min", "10");
        urlBuilder.addQueryParameter("max", "100");
        callURL = urlBuilder.build().toString();

        Request request = new Request.Builder().url(callURL).build();
        httpClient.newCall(request).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println(e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                ResponseBody responseBody = response.body();
                if (!response.isSuccessful()) {
                    throw new IOException("Error response " + response);
                } else {
                    String str = new String(responseBody.bytes());
                    System.out.println(str);
                }
            }
        });

    }

It is good practice to block UI features (buttons, etc) that rely on the WS response or use a progress dialogm which is in some way blocking but the user is aware of the ongoing request.

 

// Set progress dialog befor the call
        prgDialog = new ProgressDialog(this);
        prgDialog.setMessage("Please wait...");
        prgDialog.setCancelable(false);
        prgDialog.show();

// remove the call after successful response or error
        prgDialog.dismiss();

Step 2: Client to read British Airways data

device-2017-10-14-092750

Lets gets our hand on some real data and tap into one of the open API’s offered by some airlines and airports, though I have to say that only very few players offer their data for public consumption. I will list some API’s in another blog post. Today we will make use of the British Airways API running on the Tibco Mashery platform. BA offers a variety of flight related data and we look at the flight status webservice. We can use the service for evaluation purpose up to 1 call a second and 500 calls a day, good enough to play with their data. We will collect the status of outbound flights from LHR (London Heathrow) for +/- 2hours. Before jumping into sourcode I recommend using a tool to test the webservice manually, eg. the Chrome Browser Rest Web Service Client extension.

2017-10-14 09_24_58-Rest Web Service Client

Using Chrome extension to get data from BA Webservice

One of the challenges, every API offering might support JSON and XML as WS GET, but the attributes and response format is different, we have to implement for every airline or airport we want to use the data, or add some kind of mapping layer.

For the BA service we have to change to call from step 1 above because we need to add the API key that you apply for under your account, also the parameter are not handled as query parameters but as parameter matrix.

The first code block only lists the relevant parts and the complete code block below adds some extras to handle GUI access, exception handling and the parameter creation.

 private void testWSCall() {

        OkHttpClient httpClient = new OkHttpClient();
        String callURL = "https://api.ba.com/rest-v1/v1/flights;departureLocation=LHR;startTime=12:00;endTime=18:00;";

        Request request = new Request.Builder().url(callURL)
                .addHeader("Content-Type", "application/json")
                .addHeader("client-key", "YOUR_KEY_HERE")
                .build();

        httpClient.newCall(request).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("WS Call failed (1):" + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) {
                ResponseBody responseBody = response.body();
                if (!response.isSuccessful()) {
                    final String errRep = "WS Call failed (2):" + response.toString();
		    System.out.println(errRep);
                } else {
                    String str = new String(responseBody.bytes());
		    System.out.println(str);
                }
            }
        });

}
 private void testOKHttp() {

        OkHttpClient httpClient = new OkHttpClient();

        DateFormat dateFormatShort = new SimpleDateFormat("HH:mm");
        Date date = new Date();
        long timeNow = date.getTime();
        long timePlus = timeNow + 120 * 60 * 1000;
        long timeMinus = timeNow - 120 * 60 * 1000;
        Date datePlus = new Date(timePlus);
        Date dateMinus = new Date(timeMinus);

        String callURL = "https://api.ba.com/rest-v1/v1/flights;departureLocation=LHR;startTime=" + dateFormatShort.format(dateMinus) +";endTime=" + dateFormatShort.format(datePlus) +";";
        System.out.println(callURL);

        Request request = new Request.Builder().url(callURL)
                .addHeader("Content-Type", "application/json")
                .addHeader("client-key", "YOUR_KEY_HERE<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>")
                .build();

        httpClient.newCall(request).enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("WS Call failed (1):" + e.getMessage());
                prgDialog.dismiss();
            }

            @Override
            public void onResponse(Call call, Response response) {
                ResponseBody responseBody = response.body();
                if (!response.isSuccessful()) {
                    final String errRep = "WS Call failed (2):" + response.toString();
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            editTextResult.setText(errRep);
                        }
                    });
                    prgDialog.dismiss();
                } else {
                    try {
                        System.out.println("Response " + response.toString());
                        String str = new String(responseBody.bytes());
                        prgDialog.dismiss();

                        final JSONObject svcresponse = new JSONObject(str);
                        int spacesToIndentEachLevel = 2;
                        final String prettyPrintString = svcresponse.toString(spacesToIndentEachLevel);
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                editTextResult.setText(prettyPrintString);
                            }
                        });

                    } catch (Exception e) {
                        e.printStackTrace();
                        final String errStr = e.getMessage();
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                editTextResult.setText(errStr);
                            }
                        });
                        prgDialog.dismiss();
                    }
                }
            }
        });

    }
Advertisements

Read My Boarding Pass App

In the previous blog post I discussed the underlying standards of the BCBP (Bar Coded Boarding Pass) following IATA Reso 792. Today I will built an Android mobile app that scans the PDF417 barcode and shows the raw content as well the decoded fields.

The are 3 main challenges for building the app, scanning/reading the barcode and decoding the text to individual attributes and as last, not to use any internet connection (to assure the user the users privacy and avoid any potential identity theft discussions)

As we build a native Android app we can rely on third party libraries to scan and decode barcodes. There is a number of commercial libraries in the market, but as I build a free app I will use the zxing-android-embedded library, which is a port of the ZXing (“Zebra Crossing”) barcode scanning library for Android with added embedding features, ZXing only provides the decoding logic. Both are licensed under Apache 2.0, ZXing can decode all the common types, such as EAN-8, EAN-13, UPC, ITF, PDF417, QRCode, Aztec, Data Matrix and a few more.

Integration Barcode Library ZXing

With the library the integration becomes as simple as adding a few lines of code only.

Add the dependency to the build gradle file

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'

    compile 'com.journeyapps:zxing-android-embedded:3.5.0'
}

Trigger the scan and read the result

public void scanCode(View view){
        new IntentIntegrator(this).initiateScan();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if(result != null) {
            if(result.getContents() == null) {
                System.out.println("Scan failed or cancelled.");
            } else {
                System.out.println(result.getContents());
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

For now, the app (“ReadMyBoP – Read My Boarding Pass”) does nothing but scan the barcode, identify if it is a valid boarding pass barcode, display the raw content and makes the content more human-readable. You can download from Google Playstore. It works with Android 4.1 and above.
There is one extra feature for now, it decodes the IATA fare codes (First, business, economy classes and the various discounted codes, it follows IATA Reso 728 if you want to look for the complete codeset.

Decoding the Raw Text

Given the fact, this is a fixed-length field text, it is no big deal to split the relevant info by substring’ing it using the decoding table that we started in the previous post. As basic validation we can use the mandatory “M” on position 1 and a length of not less than 59 characters (mandatory fields).

# Element Mandatory Size Sample Remark
1 Format Code M 1 M Always “M”
2 Number of legs encoded M 1
3 Passenger Name M 20
4 Electronic Ticket Indicator M 2 E
5 Operating carrier PNR Code M 7
6 Origin IATA Code M 3 FRA Airport Code
7 Destination IATA Code M 3 SIN Airport Code
8 Operating carrier IATA Code M 3 LH Airline
9 Flight Number M 5 3456
10 Date of Flight M 3 280 Julian Date
11 Compartment Code M 1 B First, Business, Economy
12 Seat Number M 4 25A
13 Check-in Sequence Number M 5 0012
14 Passenger Status M 1 00
15 Size of optional Block M 2 5D hexadecimal
16 Start Version Number 1 > Always “>”
17 Version Number 1 5
18 Field Size of follow ing structured message 2
19 Passenger Description 1
20 Source of check-in 1
21 Source of Boarding Pass Issuance 1
22 Date of Issue of Boarding Pass (Julian Date) 4
23 Document Type 1
24 Airline Designator of boarding pass issuer 3
25 Baggage Tag Licence Plate Number 1 13
26 Baggage Tag Licence Plate Number 2 13
27 Baggage Tag Licence Plate Number 3 13
28 Field Size of follow ing structured message 2
29 Airline Numeric Code 3
30 Document Form/Serial Number 10

Is there a roadmap ? For sure, if I find the time I will add the optional fields, an airline and airport code dictionary (must check the size of a local sqllite db if we want to stay offline). Maybe add baggage tag reader feature and local barcode image storage for boarding. Stay tuned !

readmybop1.2

Application Disclaimer: The application is for educational and research purpose. It is provided as-is, no warranty included. It does not transmit data over the internet and does not store any data upon exiting the app.

Geolocation of mobile phones in the GSM network

As user of mobile phones we are used to have an almost 100% coverage for phone calls (not for data though) and the user-experience is absolutely seamless in most urban and sub-urban areas in Europe and most other countries. Moving around in trains and cars we cant sense the hand-over in the background of the GSM (2G, GPRS, EDGE), UMTS (3G), LTE (4G) network passing on our connection between the BTS ( base transceiver station). As regular user we dont have an idea about the number and location of cell towers around us, some towers or antennas are mounted on very obvious structures (antennas on towers), some are almost hidden. Though the GSM antennas can have a range of up to 35km (flat plane vs less than 5km in hilly areas), we have much higher density of cells in the urban area with antennas almost every few 100 metres or less. There are a lot of parameters influencing the infrastructure and its layout at a certain place, I wont dive into the details of if, you can get some info on the reference sites listed at the end of the article.

Rather approaching this topic hands-on, I was curious about the information that I can retrieve with Android about the active cells, its location and ultimately about the information the network operator (or other interested parties) collects about one. We might disable the GPS function of a phone to stop apps to collect our whereabouts, giving apps access to the phone state still gives a coarse location profile.

Usually phones dont reveal any network information other than the network operator but with the help of some regular Android methods of the TelephonyManager and GsmCellLocation class we get the crucial information.

The key info we are looking for is

  • MCC – Mobile Country Code
  • MNC – Mobile Network Code
  • LAC – Location Area Code
  • CELLID – The ID of the cell

Only the combination of the above 4 values is unique and can identify the location. You can look at a directory of MCC codes at Wikipedia, but there is no list of of LAC and CELLID codes published by the provider. But in the era of the “crowd” there is a collaborative community collecting the measurements of cellphones and putting them into a DB (CC-BY-SA 3.0). At  opencellid.org you can both retrieve information about cell towers as well download the complete DB.

OpenCellID.org - cell info

OpenCellID.org – cell info

I was curious how often we change the cell and what geo information I could retrieve from this, respectively build a geo profile of myself moving around. I build an app the listens to the currently active cell, lists it up on the screen and logs it into a csv files, and optionally gives an acoustic notification (beep) every time cell has changed.

MyCellDroid Beta App

MyCellDroid Beta App

Quite surprising, during one of the first tests, driving a 100km distance along the highway, both rural and suburban area, I passed through more than 80 cells !

Relevant Android methods

TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
mcc = telephonyManager.getNetworkOperator().substring(0, 3);
mnc = telephonyManager.getNetworkOperator().substring(3);
operator = telephonyManager.getNetworkOperatorName();

GsmCellLocation location = (GsmCellLocation) telephonyManager.getCellLocation();
lac = location.getLac();
cellid = location.getCid();

Be aware of the permissions required

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

A extra note about Android 6.x+ development, Google changed massively the permission concept and apps require an additional confirmation of the required permissions (for so-called dangerous permissions) during runtime, this has to be implemented specifically, otherwise your application which runs on earlier Android versions will crash. I will share the relevant implementation in an upcoming entry.

Note for Samsung Phones: The function to get information about the nearby cells is not supported by Samsung phones

List<NeighboringCellInfo> neighborCells = telephonyManager.getNeighboringCellInfo();

This list will all be null on Samsung phones. You only can retrieve information about the currently connected cell.

I will try to make some more sense out of the collected data and see how fine-grain the collected data reveals my location.

Btw, there are dozens of similar apps in the Playstore and some even report back collected data to improve and build up the opencellid project database.

References:

  1. https://en.wikipedia.org/wiki/Cell_site
  2. https://en.wikipedia.org/wiki/Base_transceiver_station
  3. https://en.wikipedia.org/wiki/Sector_antenna
  4. https://en.wikipedia.org/wiki/Cellular_network
  5. https://en.wikipedia.org/wiki/Mobile_country_code
  6. https://developer.android.com/reference/android/telephony/NeighboringCellInfo.html
  7. https://developer.android.com/reference/android/telephony/TelephonyManager.html
  8. https://developer.android.com/reference/android/telephony/gsm/GsmCellLocation.html
  9. https://developer.android.com/guide/topics/security/permissions.html

Exploring a SQLite Database on Android

or “How to read SQLite DB from a desktop”

SQLite is the relational, embedded, ACID compliant database that comes with Android. Due to this fact it is certainly the most deployed DB engine on this planet. In case your application need to have CRUD features for local persisted data and the complexity level is beyond a simple text file, you have to consider it.

A challenge is to look into the (raw) DB from your desktop (if you dont want to build and integrate a DB viewer into your app). As Android apps store databases into their respective /data subfolder and if you don’t have a rooted phone, you cant look inside this folder.

I am not aware of any tool that can open a connection to the DB remotely, so the best way is to copy the DB file into the accessible SD card (or whatever the phone and its manufacturer considers as SD card, even the internal memory mounted as SD card), download it to your desktop and open it with a tool like the SQLite DB Browser.

Let’s put some sourcecode here as reference

Create a simple demo DB

No bells and whistles, no helper classes, etc. just the most simple way to create DB and a table.

 
    private void createDB() {
        SQLiteDatabase sampleDB = this.openOrCreateDatabase("MYDEMODB", MODE_PRIVATE, null);
        sampleDB.execSQL("CREATE TABLE IF NOT EXISTS MYTABLE (Last VARCHAR, First VARCHAR, Role VARCHAR);");
        sampleDB.execSQL("INSERT INTO MYTABLE Values ('Smith','John','CEO');");
        sampleDB.execSQL("INSERT INTO MYTABLE Values ('Thomson','Allan','CTO');");
        sampleDB.close();
    }

Copy the DB to your SD card

 
    private void exportDB() {
        File mySd = Environment.getExternalStorageDirectory();
        File myData = Environment.getDataDirectory();

        FileChannel src = null;
        FileChannel des = null;
        String currentDBPath = "/data/" + getApplicationContext().getPackageName() + "/databases/MYDEMODB";
        String exportDBPath = "MYDEMODB";

        File currentDB = new File(myData, currentDBPath);
        File backupDB = new File(mySd, exportDBPath);
        try {
            src = new FileInputStream(currentDB).getChannel();
            des = new FileOutputStream(backupDB).getChannel();
            des.transferFrom(src, 0, src.size());
            src.close();
            des.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

After download to your local drive you can use the SQLite Browser to open the file. Very useful data debugging or for apps that collect data and you can’t implement an upload of the data to a server via the internet connection.

SQLite DB Browser

SQLite DB Browser (Structure View)

 

SQLite DB Browser

SQLite DB Browser (Data View)

About Google Play formerly known as Android Market

I looked at some statistics last time in 2011, almost a 4 years back. Interesting to observe the changes and the evolution.

You can find the facts at AppBrain (http://www.appbrain.com/stats)

From ~175.000 applications in 2011 the number passed 1,5 million in February 2015. Surprisingly the number of apps with in-app billing is only 108.000. It feels like almost any application comes with this “feature”, pretty much every serious game.

I also logged into the developers console again, just remembered I published  some simple apps in 2011 to learn about the physics of the appstore.

Developer Console

Developer Console

Interesting enough they were downloaded 700+ and 200+ times. Wonder what figures this experiment would render when I start it again.

Continue reading

Connecting Samsung S3 S5 to Ubuntu for debugging Android Apps

Some things did not change since the early coding days with Linux and Android Phones , you still need to tinker with system files to allow USB access to the phone. Without the below steps you get “no permission” and offline errors in the Android Device Monitor.

Android Phone Settings

  1. Out-of-the-box the phone does not allow debugging. You need to find the developer options under phone settings and specifically allow USB debugging.

    Debug Options

    Debug Options

Ubuntu Settings Continue reading