/*******************************************************************************
* Copyright 2011 The Regents of the University of California
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.ohmage.triggers.types.location;
import java.util.List;
import java.util.Locale;
import org.ohmage.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.location.Address;
import android.location.Geocoder;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
/*
* Class which draws and manages the 'add this location'
* balloon which is displayed as a view on top of the
* map view. This class can be instantiated and the show
* method can be called to display the balloon at a given
* geo-point. The address corresponding to that geo-point
* will be fetched and displayed. It also displays a button
* in the balloon view and a callback is called when the
* user clicks on the button.
*/
public class LocTrigAddLocBalloon implements OnClickListener{
//Number of times the addressing fetching
//to be tried
private static final int GEOCODING_RETRIES = 3;
//Interval between each retry
private static final long GEOCODING_RETRY_INTERVAL = 300; //ms
//Maximum height of the balloon
private static final int MAX_H = 150;
//Maximum width of the balloon
private static final int MAX_W = 350;
private Context mContext;
//The mapview instance
private MapView mMapView;
//The point on which this balloon is to be displayed
private GeoPoint mLocGP;
private LinearLayout mLayout;
private ActionListener mListener = null;
//Flag to check if this balloon is visible
private boolean mVisible = false;
public LocTrigAddLocBalloon(Context context, MapView mv) {
this.mContext = context;
this.mMapView = mv;
LayoutInflater layoutInflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Inflate the balloon layout
mLayout = (LinearLayout) layoutInflater.inflate(
R.layout.trigger_loc_maps_balloon, null);
mLayout.setVisibility(View.VISIBLE);
mLayout.setClickable(true);
}
/*
* Display this balloon at the given geo-point.
*
*/
public void show(GeoPoint gp, String action, String addr) {
if(mVisible) {
hide();
}
mLocGP = gp;
mVisible = true;
Button button = (Button) mLayout.findViewById(R.id.balloon_add_loc);
button.setOnClickListener(this);
//Set the given text to the button
button.setText(action);
button.setHeight(mMapView.getHeight() / 2 - 10);
button.invalidate();
TextView tv = (TextView) mLayout.findViewById(R.id.balloon_address);
tv.setTextColor(Color.WHITE);
mMapView.getController().animateTo(gp);
//Add the view to the map
mMapView.addView(mLayout,
new MapView.LayoutParams(
Math.min(mMapView.getWidth() - 20, MAX_W),
Math.min(mMapView.getHeight() / 2 - 10, MAX_H), gp,
MapView.LayoutParams.BOTTOM_CENTER));
//If no address passed, fetch the address
if(addr.equals("")) {
tv.setText(R.string.trigger_loc_loading_address);
ProgressBar pb = (ProgressBar) mLayout.findViewById(R.id.balloon_progress);
pb.setVisibility(ProgressBar.VISIBLE);
//Fetch the address in background
new FetchAddressTask().execute(mLocGP);
}
else {
//Address provided as argument, simply display it
onAddressFetched(addr);
}
}
/* Hide this balloon */
public void hide() {
if(mVisible) {
mMapView.removeView(mLayout);
mVisible = false;
}
}
/* Set the action listener interface */
public void setActionListener(ActionListener listener) {
this.mListener = listener;
}
/* Handle address fetch completion */
private void onAddressFetched(String addr) {
TextView tv = (TextView) mLayout.findViewById(R.id.balloon_address);
tv.setText(addr);
ProgressBar pb = (ProgressBar) mLayout.findViewById(R.id.balloon_progress);
pb.setVisibility(ProgressBar.INVISIBLE);
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.balloon_add_loc) {
//User pressed the button
if(mListener != null) {
//Invoke the callback
mListener.onAddLocationClick(mLocGP);
}
}
}
/********************** INNER CLASSES ****************************/
/* Call back to notify the 'add location' button press */
public interface ActionListener {
public void onAddLocationClick(GeoPoint gp);
}
/* Async task to fetch the address */
private class FetchAddressTask extends AsyncTask<GeoPoint, Void, String> {
protected void onPreExecute() {
}
protected String doInBackground(GeoPoint... params) {
//Get the address
Geocoder geoCoder = new Geocoder(mContext, Locale.getDefault());
for(int cTry = 0; cTry < GEOCODING_RETRIES; cTry++) {
try {
String addr = "";
List<Address> addresses = geoCoder.getFromLocation(
params[0].getLatitudeE6()/1E6,
params[0].getLongitudeE6()/1E6, 2);
//Get 2 address lines
if(addresses.size() > 0) {
int addrLines = addresses.get(0).getMaxAddressLineIndex();
if(addrLines > 0) {
addr = "";
}
for (int i=0; i<Math.min(2, addrLines); i++) {
addr += addresses.get(0).getAddressLine(i) + "\n";
}
if(addr.length() > 0) {
return addr;
}
}
}
catch (Exception e) {
}
try {
Thread.sleep(GEOCODING_RETRY_INTERVAL);
} catch (InterruptedException e) {
}
}
return new String(mContext.getString(R.string.trigger_loc_address_error));
}
protected void onPostExecute(String addr) {
onAddressFetched(addr);
}
}
/* The balloon tip layout class */
public static class BalloonTipLayout extends LinearLayout {
public BalloonTipLayout(Context context) {
super(context);
}
public BalloonTipLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void dispatchDraw(Canvas canvas) {
Paint p = new Paint();
p.setARGB(200, 100, 100, 100);
p.setStyle(Style.FILL);
p.setAntiAlias(true);
//Draw the balloon tip
Path balloonTip = new Path();
balloonTip.moveTo(getMeasuredWidth()/2 - getMeasuredWidth() * 1 / 20,
0);
balloonTip.lineTo(getMeasuredWidth()/2, getMeasuredHeight());
balloonTip.lineTo(getMeasuredWidth()/2 + getMeasuredWidth() * 1 / 20,
0);
canvas.drawPath(balloonTip, p);
super.dispatchDraw(canvas);
}
}
}