Android ChatHead Tutorial

Posted: February 11, 2014 in Android Design UI

Step 1: Setup an Android Project

Android ChatHead requires “SYSTEM_ALERT_WINDOW” permission which is available since API level 1, so minimum API 7 is fine.

Just next, next next… and create a blank activity :)

Step 2: Add ChatHead Service

If you want to know more details about ChatHead service. Read this article by Pierre-Yves Ricau.
Create a ChatHead Class that extends Service.

public class ChatHeadService extends Service {

    private WindowManager windowManager;
    private List<View> chatHeads;
    private LayoutInflater inflater;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        inflater = LayoutInflater.from(this);
        chatHeads = new ArrayList<View>();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        final View chatHead = inflater.inflate(R.layout.service_chat_head, null);

        TextView txt_title = (TextView) chatHead.findViewById(R.id.txt_title);
        TextView txt_text = (TextView) chatHead.findViewById(R.id.txt_text);

        txt_title.setText(intent.getStringExtra("title"));
        txt_text.setText(intent.getStringExtra("text"));

        chatHead.findViewById(R.id.btn_dismiss).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                windowManager.removeView(chatHead);
            }
        });

        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, 0, PixelFormat.TRANSLUCENT);

        params.gravity = Gravity.CENTER;

        chatHead.findViewById(R.id.txt_title).setOnTouchListener(new View.OnTouchListener() {
            private int initialX;
            private int initialY;
            private float initialTouchX;
            private float initialTouchY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        initialX = params.x;
                        initialY = params.y;
                        initialTouchX = event.getRawX();
                        initialTouchY = event.getRawY();
                        return true;
                    case MotionEvent.ACTION_UP:
                        return true;
                    case MotionEvent.ACTION_MOVE:
                        params.x = initialX + (int) (event.getRawX() - initialTouchX);
                        params.y = initialY + (int) (event.getRawY() - initialTouchY);
                        windowManager.updateViewLayout(chatHead, params);
                        return true;
                }
                return false;
            }
        });

        addChatHead(chatHead, params);

        return super.onStartCommand(intent, flags, startId);
    }

    public void addChatHead(View chatHead, LayoutParams params) {
        chatHeads.add(chatHead);
        windowManager.addView(chatHead, params);
    }

    public void removeChatHead(View chatHead) {
        chatHeads.remove(chatHead);
        windowManager.removeView(chatHead);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        for (View chatHead : chatHeads) {
            removeChatHead(chatHead);
        }
    }
}

Create ChatHead layout file.

/res/layout/service_chat_head.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <TextView
            android:id="@+id/txt_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#d0bb0000"
            android:padding="12dp"
            android:text="Medium Text"
            android:textColor="#d0ffffff"
            android:textSize="16sp"
            android:textStyle="bold"/>

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#d0ff0000"
            android:orientation="vertical">

        <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#d0ffffff"/>

        <TextView
                android:id="@+id/txt_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="12dp"
                android:text="TextView"
                android:textColor="#d0ffffff"/>

        <Button
                android:id="@+id/btn_dismiss"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="6dp"
                android:text="Dismiss"
                android:textColor="#d0ffffff"
                android:textStyle="bold"/>
    </LinearLayout>

</LinearLayout>

Add ChatHeadService to AndroidManifest.xml

<service android:name=”com.gbinghan.androidchathead.ChatHeadService” ></service>

Add SYSTEM_ALERT_WINDOW to AndroidManifest.xml

<uses-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW” />

Your AndroidManifest.xml should look like this.

Step 3: Add a button to launch the service :)

Add OnClickListener in Activity file java/com/gbinghan/androidchathead/MainActivity.java.

findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, ChatHeadService.class);
intent.putExtra(“title”, “Hello”);
intent.putExtra(“text”, “ChatHead”);
startService(intent);
}
});

Add the button in layout file /res/layout/activity_main.xml.

    <Button
android:id=”@+id/button1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/textView1″
android:layout_below=”@+id/textView1″
android:text=”Button”/>

Step 4: Run!

You should see this on your emulator/device.

Note:
If you use different package name, remember to replace all instance of “gbinghan”.

References:
http://www.piwai.info/chatheads-basics/
https://github.com/venator85/ChatHeads/blob/master/src/com/example/chatheads/MainActivity.java

In this article we will develop an Android application that demonstrates how to create a searchview widget in action bar using ActionBarCompat library.

This application facilitates users to search places using Google Places API. As user types in place name in searchview, places that matches the typed in characters will be listed as suggestions. On selecting an item from the suggestion, the corresponding place will be shown in the Google Maps Android API V2.

We need following for this application :

ActionBarCompat library : We know that, SearchView widget is only available since Android API Level 11 and its previous versions does not have the SearchView widget. In order for the backward compatibility, we can use ActionBarCompat library which is available with Android Support library revision 18.

Google Play Services Library : Since we are using Google Maps Android API V2 with this application, we need Google Play Service library for this application to work.

Configuration file for SearchView widget : This configuration file is used to configure the searchview widget. For this application, this file is available at res/xml/searchable.xml

Searchable Activity : An activity that handles the search requests from searchview widget. In this application, MainActivity acts as the searchable activity.

Content Provider : The searchview widget performs the search operation on the data provided by the content provider.

Loaders : Loaders are used to execute tasks in background ( non-ui thread ) . We are using two loaders in the MainActivity class. One loader is used in the method doSearch() which is invoked when user presses the “Go” button of the soft keyboard. The second loader is used in the method “getPlace()”, which is invoked when an item in the search suggestion list is selected.

Activity Style : The activity that implements the action bar should apply the style Theme.AppCompat or Theme.AppCompat.Light  or Theme.AppCompat.DarkActionBar. This can be done in AndroidManifest.xml file.

This application is developed in Eclipse 4.2.0 with ADT plugin 22.0.5, Android Support library 18 and Google Play Service library 9.

Please jump to end of this article to see the screenshots of this application.


1. Create new Android application project namely “LocationActionBarCompatSearchViewV2″

Create new Android application project

Figure 1 : Create new Android application project


2. Configure the application

Configure the application project

Figure 2 : Configure the application project


3. Design application launcher icon

Design application launcher icon

Figure 3 : Design application launcher icon


4. Create a blank activity

Create a blank activity

Figure 4 : Create a blank activity


5. Enter MainActivity details

Enter MainActivity Details

Figure 5 : Enter MainActivity Details


 6. Setup ActionBarCompat library in Eclipse IDE

Please refer the article titled “Android – Setting up ActionBarCompat support library in Eclipse


7. Download and configure Google Play Services Library in Eclipse

Please follow the given below link to setup Google Play Service library in Eclipse.

http://developer.android.com/google/play-services/setup.html


8. Link this project to ActionBarCompat library and Google Play Service llibrary

Linking this application with Google Play Services library and Action Bar Compat library

Figure 6 : Linking this application with Google Play Services library and Action Bar Compat library


9. Get the API key for Google Maps Android API V2

We need to get an API key from Google to use Google Maps in Android application.

Please follow the given below link to get the API key for Google Maps Android API v2.

https://developers.google.com/maps/documentation/android/start


10. Get the API key for Google Places API

We can create API key for Google Place API by clicking “Create new Browser key”  available at the “API Access” pane of the Google console URL : http://code.google.com/apis/console.

Also ensure that, “Places API” is enabled in the “Services” pane of the Google console.


11. Update the file res/values/strings.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">SearchViewWithActionBarCompat</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_search">Search</string>
    <string name="search_hint">Search Places</string>
    <string name="search_settings">Search Places</string>
</resources>

12. Update the layout file res/layout/activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
<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"
    tools:context=".MainActivity" >
    <fragment
        android:id="@+id/map"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

13. Update the menu file res/menu/main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    <item
        android:id="@+id/action_search"
        android:orderInCategory="100"
        android:title="@string/action_search"
        myapp:actionViewClass="android.support.v7.widget.SearchView"
        myapp:showAsAction="always" />
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:showAsAction="never"
        android:title="@string/action_settings"/>
</menu>

14. Create the class file src/in/wptrafficanalyzer/locationactionbarcompatsearchviewv2/PlaceJSONParser.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package in.wptrafficanalyzer.locationactionbarcompatsearchviewv2;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class PlaceJSONParser {
    /** Receives a JSONObject and returns a list */
    public List<HashMap<String,String>> parse(JSONObject jObject){
        JSONArray jPlaces = null;
        try {
            /** Retrieves all the elements in the 'places' array */
            jPlaces = jObject.getJSONArray("predictions");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        /** Invoking getPlaces with the array of json object
        * where each json object represent a place
        */
        return getPlaces(jPlaces);
    }
    private List<HashMap<String, String>> getPlaces(JSONArray jPlaces){
        int placesCount = jPlaces.length();
        List<HashMap<String, String>> placesList = new ArrayList<HashMap<String,String>>();
        HashMap<String, String> place = null;
        /** Taking each place, parses and adds to list object */
        for(int i=0; i<placesCount;i++){
            try {
                /** Call getPlace with place JSON object to parse the place */
                place = getPlace((JSONObject)jPlaces.get(i));
                placesList.add(place);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return placesList;
    }
    /** Parsing the Place JSON object */
    private HashMap<String, String> getPlace(JSONObject jPlace){
        HashMap<String, String> place = new HashMap<String, String>();
        String id="";
        String reference="";
        String description="";
        try {
            description = jPlace.getString("description");
            id = jPlace.getString("id");
            reference = jPlace.getString("reference");
            place.put("description", description);
            place.put("_id",id);
            place.put("reference",reference);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return place;
    }
}

15. Create the class file src/in/wptrafficanalyzer/locationactionbarcompatsearchviewv2/PlaceDetailsJSONParser.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package in.wptrafficanalyzer.locationactionbarcompatsearchviewv2;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.json.JSONException;
import org.json.JSONObject;
public class PlaceDetailsJSONParser {
    /** Receives a JSONObject and returns a list */
    public List<HashMap<String,String>> parse(JSONObject jObject){
        Double lat = Double.valueOf(0);
        Double lng = Double.valueOf(0);
        String formattedAddress = "";
        HashMap<String, String> hm = new HashMap<String, String>();
        List<HashMap<String, String>> list = new ArrayList<HashMap<String,String>>();
        try {
            lat = (Double)jObject.getJSONObject("result").getJSONObject("geometry").getJSONObject("location").get("lat");
            lng = (Double)jObject.getJSONObject("result").getJSONObject("geometry").getJSONObject("location").get("lng");
            formattedAddress = (String) jObject.getJSONObject("result").get("formatted_address");
        } catch (JSONException e) {
            e.printStackTrace();
        }catch(Exception e){
            e.printStackTrace();
        }
        hm.put("lat", Double.toString(lat));
        hm.put("lng", Double.toString(lng));
        hm.put("formatted_address",formattedAddress);
        list.add(hm);
        return list;
    }
}

16. Create the file src/in/wptrafficanalyzer/locationactionbarcompatsearchviewv2/PlaceProvider.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
package in.wptrafficanalyzer.locationactionbarcompatsearchviewv2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.util.Log;
public class PlaceProvider extends ContentProvider {
    public static final String AUTHORITY = "in.wptrafficanalyzer.locationsherlocksearchviewmapv2.PlaceProvider";
    public static final Uri SEARCH_URI = Uri.parse("content://"+AUTHORITY+"/search");
    public static final Uri DETAILS_URI = Uri.parse("content://"+AUTHORITY+"/details");
    private static final int SEARCH = 1;
    private static final int SUGGESTIONS = 2;
    private static final int DETAILS = 3;
    // Obtain browser key from https://code.google.com/apis/console
    String mKey = "key=YOUR_BROWSER_KEY";
    // Defines a set of uris allowed with this content provider
    private static final UriMatcher mUriMatcher = buildUriMatcher();
    private static UriMatcher buildUriMatcher() {
        UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // URI for "Go" button
        uriMatcher.addURI(AUTHORITY, "search", SEARCH );
        // URI for suggestions in Search Dialog
        uriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY,SUGGESTIONS);
        // URI for Details
        uriMatcher.addURI(AUTHORITY, "details",DETAILS);
        return uriMatcher;
    }
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
        Cursor c = null;
        PlaceJSONParser parser = new PlaceJSONParser();
        PlaceDetailsJSONParser detailsParser = new PlaceDetailsJSONParser();
        String jsonString = "";
        String jsonPlaceDetails = "";
        List<HashMap<String, String>> list = null;
        List<HashMap<String, String>> detailsList = null;
        MatrixCursor mCursor = null;
        switch(mUriMatcher.match(uri)){
        case SEARCH:
            // Defining a cursor object with columns description, lat and lng
            mCursor = new MatrixCursor(new String[] { "description","lat","lng" });
            // Create a parser object to parse places in JSON format
            parser = new PlaceJSONParser();
            // Create a parser object to parse place details in JSON format
            detailsParser = new PlaceDetailsJSONParser();
            // Get Places from Google Places API
            jsonString = getPlaces(selectionArgs);
            try {
                // Parse the places ( JSON => List )
                list = parser.parse(new JSONObject(jsonString));
                // Finding latitude and longitude for each places using Google Places Details API
                for(int i=0;i<list.size();i++){
                    HashMap<String, String> hMap = (HashMap<String, String>) list.get(i);
                    detailsParser =new PlaceDetailsJSONParser();
                    // Get Place details
                    jsonPlaceDetails = getPlaceDetails(hMap.get("reference"));
                    // Parse the details ( JSON => List )
                    detailsList = detailsParser.parse(new JSONObject(jsonPlaceDetails));
                    // Creating cursor object with places
                    for(int j=0;j<detailsList.size();j++){
                        HashMap<String, String> hMapDetails = detailsList.get(j);
                        // Adding place details to cursor
                        mCursor.addRow(new String[]{ hMap.get("description") , hMapDetails.get("lat") , hMapDetails.get("lng") });
                    }
                }
            } catch (JSONException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            c = mCursor;
            break;
        case SUGGESTIONS :
            // Defining a cursor object with columns id, SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_INTENT_EXTRA_DATA
            mCursor = new MatrixCursor(new String[] { "_id", SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA } );
            // Creating a parser object to parse places in JSON format
            parser = new PlaceJSONParser();
            // Get Places from Google Places API
            jsonString = getPlaces(selectionArgs);
            try {
                // Parse the places ( JSON => List )
                list = parser.parse(new JSONObject(jsonString));
                // Creating cursor object with places
                for(int i=0;i<list.size();i++){
                    HashMap<String, String> hMap = (HashMap<String, String>) list.get(i);
                    // Adding place details to cursor
                    mCursor.addRow(new String[] { Integer.toString(i), hMap.get("description"), hMap.get("reference") });
                }
            } catch (JSONException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            c = mCursor;
            break;
        case DETAILS :
            // Defining a cursor object with columns description, lat and lng
            mCursor = new MatrixCursor(new String[] { "description","lat","lng" });
            detailsParser = new PlaceDetailsJSONParser();
            jsonPlaceDetails = getPlaceDetails(selectionArgs[0]);
            try {
                detailsList = detailsParser.parse(new JSONObject(jsonPlaceDetails));
            } catch (JSONException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            for(int j=0;j<detailsList.size();j++){
                HashMap<String, String> hMapDetails = detailsList.get(j);
                mCursor.addRow(new String[]{ hMapDetails.get("formatted_address") , hMapDetails.get("lat") , hMapDetails.get("lng") });
            }
            c = mCursor;
            break;
        }
        return c;
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }
    @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        return false;
    }
    @Override
    public int update(Uri uri, ContentValues values, String selection,
        String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }
    /** A method to download json data from url */
    private String downloadUrl(String strUrl) throws IOException{
        String data = "";
        InputStream iStream = null;
        HttpURLConnection urlConnection = null;
        try{
            URL url = new URL(strUrl);
            // Creating an http connection to communicate with url
            urlConnection = (HttpURLConnection) url.openConnection();
            // Connecting to url
            urlConnection.connect();
            // Reading data from url
            iStream = urlConnection.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(iStream));
            StringBuffer sb = new StringBuffer();
            String line = "";
            while( ( line = br.readLine()) != null){
                sb.append(line);
            }
            data = sb.toString();
            br.close();
        }catch(Exception e){
           Log.d("Exception while downloading url", e.toString());
        }finally{
            iStream.close();
            urlConnection.disconnect();
        }
        return data;
    }
    private String getPlaceDetailsUrl(String ref){
        // reference of place
        String reference = "reference="+ref;
        // Sensor enabled
        String sensor = "sensor=false";
        // Building the parameters to the web service
        String parameters = reference+"&"+sensor+"&"+mKey;
        // Output format
        String output = "json";
        // Building the url to the web service
        String url = "https://maps.googleapis.com/maps/api/place/details/"+output+"?"+parameters;
        return url;
    }
    private String getPlacesUrl(String qry){
        try {
            qry = "input=" + URLEncoder.encode(qry, "utf-8");
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        // Sensor enabled
        String sensor = "sensor=false";
        // place type to be searched
        String types = "types=geocode";
        // Building the parameters to the web service
        String parameters = qry+"&"+types+"&"+sensor+"&"+mKey;
        // Output format
        String output = "json";
        // Building the url to the web service
        String url = "https://maps.googleapis.com/maps/api/place/autocomplete/"+output+"?"+parameters;
        return url;
    }
    private String getPlaces(String[] params){
        // For storing data from web service
        String data = "";
        String url = getPlacesUrl(params[0]);
        try{
            // Fetching the data from web service in background
            data = downloadUrl(url);
        }catch(Exception e){
            Log.d("Background Task",e.toString());
        }
        return data;
    }
    private String getPlaceDetails(String reference){
        String data = "";
        String url = getPlaceDetailsUrl(reference);
        try {
            data = downloadUrl(url);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }
}

Note : Update “YOUR_BROWSER_KEY on line 39 with the browser key obtained in Step 10.


17. Update the class MainActivity in the file src/in/wptrafficanalyzer/locationactionbarcompatsearchviewv2/MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package in.wptrafficanalyzer.locationactionbarcompatsearchviewv2;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MainActivity extends ActionBarActivity implements LoaderCallbacks<Cursor>{
    GoogleMap mGoogleMap;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SupportMapFragment fragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
        mGoogleMap = fragment.getMap();
        handleIntent(getIntent());
    }
    private void handleIntent(Intent intent){
        if(intent.getAction().equals(Intent.ACTION_SEARCH)){
            doSearch(intent.getStringExtra(SearchManager.QUERY));
        }else if(intent.getAction().equals(Intent.ACTION_VIEW)){
            getPlace(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
        }
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        handleIntent(intent);
    }
    private void doSearch(String query){
        Bundle data = new Bundle();
        data.putString("query", query);
        getSupportLoaderManager().restartLoader(0, data, this);
    }
    private void getPlace(String query){
        Bundle data = new Bundle();
        data.putString("query", query);
        getSupportLoaderManager().restartLoader(1, data, this);
    }
    @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);
        // Get the SearchView and set the searchable configuration
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        MenuItem searchItem = menu.findItem(R.id.action_search);
        SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
        // searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        // Assumes current activity is the searchable activity
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        // searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
        return super.onCreateOptionsMenu(menu);
    }
    @Override
    public android.support.v4.content.Loader<Cursor> onCreateLoader(int arg0,
        Bundle query) {
        CursorLoader cLoader = null;
        if(arg0==0)
            cLoader = new CursorLoader(getBaseContext(), PlaceProvider.SEARCH_URI, null, null, new String[]{ query.getString("query") }, null);
        else if(arg0==1)
            cLoader = new CursorLoader(getBaseContext(), PlaceProvider.DETAILS_URI, null, null, new String[]{ query.getString("query") }, null);
        return cLoader;
    }
    @Override
    public void onLoadFinished(android.support.v4.content.Loader<Cursor> arg0,
        Cursor c) {
        showLocations(c);
    }
    @Override
    public void onLoaderReset(android.support.v4.content.Loader<Cursor> arg0) {
        // TODO Auto-generated method stub
    }
    private void showLocations(Cursor c){
        MarkerOptions markerOptions = null;
        LatLng position = null;
        mGoogleMap.clear();
        while(c.moveToNext()){
            markerOptions = new MarkerOptions();
            position = new LatLng(Double.parseDouble(c.getString(1)),Double.parseDouble(c.getString(2)));
            markerOptions.position(position);
            markerOptions.title(c.getString(0));
            mGoogleMap.addMarker(markerOptions);
        }
        if(position!=null){
            CameraUpdate cameraPosition = CameraUpdateFactory.newLatLng(position);
            mGoogleMap.animateCamera(cameraPosition);
        }
    }
}

18. Update the configuration file /res/xml/searchable.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_hint"
    android:searchSettingsDescription="@string/search_settings"
    android:searchSuggestAuthority="in.wptrafficanalyzer.locationactionbarcompatsearchviewv2.PlaceProvider"
    android:searchSuggestIntentAction="android.intent.action.VIEW"
    android:searchSuggestSelection=" ?"
    android:searchSuggestThreshold="2" >
</searchable>

19. Update the file AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?xml version="1.0" encoding="utf-8"?>
    package="in.wptrafficanalyzer.locationactionbarcompatsearchviewv2"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <!-- Protect the map component of the application using application signature -->
    <permission
        android:name="in.wptrafficanalyzer.locationactionbarcompatsearchviewv2.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />
    <!-- Allows to receive map -->
    <uses-permission android:name="in.wptrafficanalyzer.locationactionbarcompatsearchviewv2.permission.MAPS_RECEIVE" />
    <!-- Used by the Google Maps Android API V2 to download map tiles from Google Maps servers -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Allows the Google Maps Android API V2 to cache map tile data in the device's external storage area -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- Allows the Google Maps Android API V2 to use WiFi or mobile cell data (or both) to determine the device's location -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- Allows the Google Maps Android API V2 to use the Global Positioning System (GPS)
    to determine the device's location to within a very small area -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <!-- Allows to contact Google Serves -->
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <!-- Google Maps Android API V2 requires OpenGL ES version 2 -->
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
        <activity
            android:name="in.wptrafficanalyzer.locationactionbarcompatsearchviewv2.MainActivity"
            android:label="@string/app_name"
            android:launchMode="singleTop" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <!-- Points to searchable activity -->
            <meta-data android:name="android.app.default_searchable"
                android:value=".MainActivity" />
            <!-- Points to searchable meta data -->
            <meta-data android:name="android.app.searchable"
                android:resource="@xml/searchable"/>
        </activity>
        <provider
            android:name=".PlaceProvider"
            android:authorities="in.wptrafficanalyzer.locationactionbarcompatsearchviewv2.PlaceProvider"
            android:exported="false" />
        <!-- Specifies the Android API Key, which is obtained from Google API Console -->
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="YOUR_ANDROID_API_KEY" />
    </application>
</manifest>

Note : Update “YOUR_ANDROID_API_KEY on line 76 with the Android API key obtained in Step 9.


20. Screenshots of the application

Searching places in SearchView Widget

Figure 7 : Searching places in SearchView Widget


Showing selected place in Google Maps

Figure 8 : Showing selected place in Google Maps


21. Download Source code

Source : http://wptrafficanalyzer.in/blog/android-searchview-widget-with-actionbarcompat-library/

10 Attractive android tutorials for developers

I selected some pretty cool and amazing tutorials for developers who going to learn how to make applications for android. For beginners and advanced developers. Some are Tutorials for drawing, some are for using location service, another – using SDK. Hope you find it Useful !

1. OpenGL ES 2.0

This tutorial shows you how to create a simple Android application that uses the OpenGL ES 2.0 API to perform some basic graphics operations.
OpenGL ES

2. Android Development Tutorial

This tutorial describes how to create Android applications with Eclipse. This is very long tutorial, but also most detailed.
Android Development Tutorial

3. Android SDK Tutorial – Learn how to install Android SDK

Tutorial describes how to install the Android SDK and set up your development environment for the first time.
Android SDK Tutorial

4. Using Facebook SDK in Android development

In this tutorial I show you an android application, which logging in to Facebook, then get the Facebook ID.
Using Facebook SDK

5. Drawing with Canvas in Android, Saving your drawings

This is one of tutorials from series of topics for drawing with canvas on android.
Drawing with Canvas

6. Android Google Maps Tutorial

In this tutorial we will see how to incorporate Google Maps into an Android app.
Android Google Maps Tutorial

7. Android location service example

This tutorial will help you to start using location services (in particular: LocationManager class to get user location and Geocoder to translate location into addresses) and Google Maps on Android.
Android location service example

8. Working With Android Contacts

Learn to work with the Android contacts database. Basic knowledge of accessing SQLite in Android along with using Cursors is expected.
Working With Android Contacts

9. How to calculate the distance between two GPS coordinates?

A simple function that takes two GPS coordinates as input and outputs the distance between them in meter.

10. How To Create Android Live Wallpaper

In this article tells how to create live wallpaper from scratch. Step-by-step, we will create live wallpaper that would output TV test pattern on out home screen.
How To Create Android Live Wallpaper

From http://www.script-tutorials.com/10-attractive-android-tutorials-for-developers/

I would like to adjust/re-size the layout when the soft-keyboard activated, as below:

Before and After:

Found couple resources in SO:

  1. How to keep all fields and texts visible while the soft keyboard is shown
  2. android soft keyboard spoils layout when appears
  3. Adjust layout when soft keyboard is on

But the questions & answers are rather ambiguous, here’s the question with clearer picture of what I want.

Requirements:

  • It should work on phone with any screen sizes.
  • Noticed that the margin/padding space at “FACEBOOK” and “Sign Up for Facebook” has changed before and after.
  • No scroll view is involved.

——————————————————————————————————–

android:windowSoftInputMode=”adjustResize”

in the activity decalration in manifest will adjust the layout resize option …

enter image description here

some source code below for layout design

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:text="FaceBook"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="30dp"
        android:ems="10"
        android:hint="username" >

        <requestFocus />
    </EditText>

    <EditText
        android:id="@+id/editText2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/editText1"
        android:layout_marginTop="20dp"
        android:ems="10"
        android:hint="password" />

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/editText2"
        android:layout_centerHorizontal="true"
        android:layout_marginLeft="18dp"
        android:layout_marginTop="20dp"
        android:text="Log In" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginTop="17dp"
        android:gravity="center"
        android:text="Sign up for facebook"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

You have probably already used FrameLayout (doc) for what it’s named, adding a decoration around other content element. It can actually be much more than that and is probably one of the most versatile container of all.

The secret of FrameLayout is how it layouts its children. Although normally designed to contain one item, it will happily stack up other element on top of each other. Thus FrameLayout is essentially a way to manipulate the Z-order of views on the screen.

This is super useful for a couple of UI tricks from HUD-like elements to sliding panels to more complex animated transitions. In this post we will see an example for each of those.

Overlay Elements

As I mentioned, FrameLayout will automatically stacks its children on top of each other. This makes it really easy to implement overlay or HUD element in your interface.

The idea is to wrap the part of the UI into an outer FrameLayout instance. Generally this will be the root element of your layout. You can then add below the main layout definition the other elements you want to overlay.

These elements will generally have a fixed size or be set as wrap_content. You can then place them at the right position on screen using the layout_gravity and layout_margin XML attributes.

In the following screenshot, my main content is a Google Map view. I have then added two overlays above it, one that replicate a toast message (center-bottom corner) and a TextView displaying the last time the data was loaded (upper-right corner).

This is simply achieved with the following layout:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- The main content -->
    <com.google.android.gms.maps.MapView
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- "Loaded" flash bar layout -->
    <FrameLayout
        android:id="@+id/FlashBarLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|bottom"
        android:layout_marginBottom="72dp">
        <!-- flash bar content -->
    </FrameLayout>
    <!-- Last loaded time layout -->
    <TextView
        android:id="@+id/UpdateTimeText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top|right"
        android:layout_marginTop="4dp"
        android:layout_marginRight="4dp" />
</FrameLayout>

Being just normal views, you can manipulate the overlays as usual. For instance you will probably want to implement some sort of animations for transition between visibility states if the overlay is transient.

Sliding Panels

Sliding panels (aka drawers, aka fly-out menus) are the rage these days. They mainly consist of a panel that is placed, depending on the desired spatial effect, above or below another piece of content. Again this falls back to placing elements using a FrameLayout object.

The trick then is to use the translationX and translationY properties of the panel View to move the element back and forth.

Alternatively, View also exposes the OffsetTopAndBottom and OffsetLeftAndRight methods (working on display list directly) but those are not as easy to animate. Indeed, with the translation properties you can directly useObjectAnimator or ViewPropertyAnimator.

The panel is initially “hidden” by setting its translationY property to its height (i.e. top side forced to the bottom of the screen). We create the initial appearing effect with the following code piece inside the panel class:

// As its name imply, this interpolator will let the view go slightly overboard
// and then spring it back to its place
var intp = new Android.Views.Animations.OvershootInterpolator ();
var d = Context.Resources.GetInteger (Android.Resource.Integer.ConfigMediumAnimTime);
// Only the header part of the view should be visible, we thus just make
// the bottom of the view translated off-screen
var tY = Height - FindViewById (Resource.Id.PaneHeaderView).Height;

this.Animate ().TranslationY (tY).SetDuration (d).SetInterpolator (intp).Start ();

A common idiom of those panel is also to draw a shadow on the edge where they meet the content. This can be implemented very efficiently by overriding the DispatchDraw method of the sliding panel View and drawing a black linear GradientDrawable at the right place (use the Translate method on Canvas for the right positioning).

protected override void DispatchDraw (Android.Graphics.Canvas canvas)
{
    base.DispatchDraw (canvas);
    if (shadowDrawable == null)
        shadowDrawable = new GradientDrawable (GradientDrawable.Orientation.BottomTop,
                                               new[] { Color.Argb (0x60, 0, 0, 0), Color.Argb (0, 0, 0, 0) });
    // The reserved area for the shadow is set with top padding on the view.
    shadowDrawable.SetBounds (0, 0, Width, PaddingTop);
    shadowDrawable.Draw (canvas);
}

Transitions

With the move to a Fragment world, everything becomes just another View to manage and thereby removing the need for Activity.

When you think about the different “screen” of you application as individual fragments it becomes very easy to manipulate them in fun ways. Notably, being just managed View objects, you can apply any of the animations techniques you are already familiar with to implement very nifty transition effects.

At the core of such system, you will find again the same idea of a central, expanded FrameLayout surface where yourFragment are added.

The idea is to pile the Fragment views on top of each other like a deck of card. The current screen is thus the first card of the pile and application transitions are then simply how you want the middle cards to be put on top.

I’m using this analogy of the card deck because the method we are going to use here is View.BringToFront. This method will move the view at the last position inside its parent array. The end result of that operation of course depends on the type of container but in the case of FrameLayout it will make the View be drawn on top.

Note that this method doesn’t force a relayout which is fine for FrameLayout since children individual bounds aren’t dependent on each other but in the case of e.g. LinearLayout you will have to call RequestLayout for the modification to happen.

We will assume the layout of our main Activity is a single full-size FrameLayout container. Our application is composed of two screens represented as two fragments.

Initially, we create and attach directly those fragments to our content frame, hiding the second one to get into an initial state:

FirstFragment fragment1;
SecondFragment fragment2;

Fragment currentFragment;

protected override void OnCreate (Bundle bundle)
{
	SetContentView (/* ... */);

	fragment1 = new FirstFragment (this);
	fragment2 = new SecondFragment (this);

	SupportFragmentManager.BeginTransation ()
		.Add (Resource.Id.content_frame, fragment2, SecondFragment.Name)
		.Hide (fragment2)
		.Add (Resource.Id.content_frame, fragment1, FirstFragment.Name)
		.Commit ();
	currentFragment = fragment1;
}

When we want to change the Fragment that is shown, we will make sure that both views are on top of the drawing pile with the current one being on top of the other using BringToFront. We then swap the two fragments in aFragmentTransaction:

void SwitchTo (Fragment fragment, string name)
{
	if (fragment.IsVisible)
		return;
	var t = SupportFragmentManager.BeginTransaction ();
	t.SetCustomAnimations (Resource.Animator.frag_slide_in,
	                       Resource.Animator.frag_slide_out);

	// Make sure the next view is below the current one
	fragment.View.BringToFront ();
	// And bring the current one to the very top
	currentFragment.View.BringToFront ();

	// Hide the current fragment
	t.Hide (currentFragment);
	t.Show (existingFragment);
	currentFragment = existingFragment;

	// You probably want to add the transaction to the backstack
	// so that user can use the back button
	t.AddToBackStack (null);
	t.Commit ();
}

To add a bit of woosh, we are using customs animations to mark the transition between the two fragments to get this result:

These animations need to be defined in an XML resource using either property animator (aka from Android.Animations) if you are using framework Fragment or using view animations (aka from Android.View.Animations) if you are using Fragment from the support package.

To register custom animations, we call the SetCustomAnimations method on FragmentTransaction like so:

t.SetCustomAnimations (Resource.Animator.frag_slide_in,
                       Resource.Animator.frag_slide_out);

The first animation will be run on any fragment added/attached/shown while the second animation will be played on fragments hidden/detached/removed. Both are ran at the same time but you can use the animation startOffsetattribute to delay one or the other.

For instance in my example, my frag_slide_out animation is defined like so:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:interpolator="@android:interpolator/accelerate_cubic">
   <translate
      android:fromXDelta="0"
      android:toXDelta="100%"
      android:duration="300" />
   <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.3"
        android:duration="300" />
</set>

Conclusion

Everything I have presented here is part of a big refresh to Moyeu. You can view the full source code on the GitHub repository at garuma/Moyeu.