at.ac.uniklu.mobile.sportal.MapActivity.java Source code

Java tutorial

Introduction

Here is the source code for at.ac.uniklu.mobile.sportal.MapActivity.java

Source

/*
 * Copyright (c) 2014 Mario Guggenberger <mario.guggenberger@aau.at>
 *
 * This file is part of AAU Studentenportal.
 *
 * AAU Studentenportal is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * AAU Studentenportal is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with AAU Studentenportal.  If not, see <http://www.gnu.org/licenses/>.
 */

package at.ac.uniklu.mobile.sportal;

import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import at.ac.uniklu.mobile.sportal.ui.AlertDialogOnClickDismissHandler;
import at.ac.uniklu.mobile.sportal.ui.ProgressNotificationToggle;
import at.ac.uniklu.mobile.sportal.ui.WebViewTimeoutClient;
import at.ac.uniklu.mobile.sportal.util.ActionBarHelper;
import at.ac.uniklu.mobile.sportal.util.Analytics;

public class MapActivity extends Activity implements ProgressNotificationToggle {

    private static final String TAG = "MapActivity";
    private static final String MAP_URL = "http://url.to/map/";

    private static final int DIALOG_SEARCHING = 1;

    private static final int SEARCHMODE_DEFAULT = 0;
    private static final int SEARCHMODE_ACCESSIBLE_WHEELCHAIR = 1;

    private ActionBarHelper mActionBar;
    private WebView mWebView;
    private Gson mGson;

    private LayerManager mCampusLayers;
    private LayerManager mPoiLayers;
    private AutoCompleteTextView mSearchText;
    private View[] mSearchPanels;
    private int mActiveSearchPanel = 0;
    private AutoCompleteTextView[] mSearchTexts;
    private int mSearchMode = SEARCHMODE_DEFAULT;

    private Handler mHandler = new Handler();

    private boolean mPendingSearch;
    private String mPendingSearchQuery;

    @TargetApi(11)
    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map);
        mActionBar = new ActionBarHelper(this).setupHeader();

        mGson = new GsonBuilder().create();

        mWebView = (WebView) findViewById(R.id.web_view);
        mWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY); // hide right scroll border on android < 3
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setBuiltInZoomControls(false);
        mWebView.getSettings().setUseWideViewPort(false);
        mWebView.addJavascriptInterface(new SpJavaScriptInterface(), "sp");
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1
                && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            /* disable hardware acceleration:
             * - results in better rendering performance on Android 3.x & 4.0
             * - avoids webkit bugs in Android 4.x with OpenLayers and Leaflet
             */
            mWebView.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
        }
        mWebView.setWebViewClient(new WebViewTimeoutClient(30000) {
            private boolean timeout;

            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                progressNotificationOn();
                timeout = false;
            }

            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                progressNotificationOff();
                if (failingUrl != null && failingUrl.equals(MAP_URL)) {
                    showTimeoutMessage();
                }
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);

                if (MapActivity.this.isFinishing()) {
                    /* do nothing if activity has already been closed
                     * (avoids android.view.WindowManager$BadTokenException because of
                     * a following showDialog() call)
                     */
                    return;
                }

                // this method is only called once since the remaining data is loaded asynchronously
                progressNotificationOff();
                if (!timeout) {
                    getCampusLayers();
                    getPoiLayers();
                    if (mPendingSearch) {
                        mPendingSearch = false;
                        searchRoom(mPendingSearchQuery);
                    }
                }
            }

            @Override
            public void onTimeout() {
                Log.d(TAG, "timeout");
                timeout = true;
                mWebView.stopLoading();
                showTimeoutMessage();
                Analytics.onEvent(Analytics.EVENT_MAP_UNAVAILABLE);
            }
        });

        View uniPositionButton = findViewById(R.id.uni_position);
        uniPositionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                zoomToUni();
            }
        });

        View searchButton = findViewById(R.id.search);
        searchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSearchPanels[mActiveSearchPanel].getVisibility() != View.GONE) {
                    onSearchRequested(); // hide current form
                }
                mActiveSearchPanel = 0;
                onSearchRequested();
            }
        });

        View routeButton = findViewById(R.id.route);
        routeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mSearchPanels[mActiveSearchPanel].getVisibility() != View.GONE) {
                    onSearchRequested(); // hide current form
                }
                mActiveSearchPanel = 1;
                onSearchRequested();
            }
        });

        View currentPositionButton = findViewById(R.id.current_position);
        currentPositionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
                Location location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                if (location == null) {
                    Toast.makeText(MapActivity.this, R.string.map_error_nolocation, Toast.LENGTH_LONG).show();
                } else {
                    Log.d(TAG, location.toString());
                    panTo(location.getLongitude(), location.getLatitude());
                }
            }
        });

        View campusLayersButton = findViewById(R.id.campuslayers);
        campusLayersButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mCampusLayers == null || mCampusLayers.isEmpty())
                    return;

                AlertDialog.Builder builder = new AlertDialog.Builder(MapActivity.this);
                builder.setTitle(R.string.map_floors);
                builder.setSingleChoiceItems(mCampusLayers.getListItems(),
                        mCampusLayers.getListSingleSelectionIndex(), new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int item) {
                                dialog.dismiss();
                                showCampusLayer(item);
                                mCampusLayers.deselectAll();
                                mCampusLayers.select(item);
                                Analytics.onEvent(Analytics.EVENT_MAP_LAYERSWITCH_CAMPUS, "layer",
                                        mCampusLayers.get(item).name);
                            }
                        });
                builder.setNeutralButton(R.string.close, new AlertDialogOnClickDismissHandler());
                builder.create().show();
            }
        });

        View poiLayersButton = findViewById(R.id.poilayers);
        poiLayersButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mPoiLayers == null || mPoiLayers.isEmpty())
                    return;

                AlertDialog.Builder builder = new AlertDialog.Builder(MapActivity.this);
                builder.setTitle(R.string.map_pois);
                builder.setMultiChoiceItems(mPoiLayers.getListItems(), mPoiLayers.getListMultiSelection(),
                        new DialogInterface.OnMultiChoiceClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                                dialog.dismiss();
                                if (isChecked) {
                                    showPoiLayer(which);
                                    Analytics.onEvent(Analytics.EVENT_MAP_LAYERSWITCH_POI, "layer",
                                            mPoiLayers.get(which).name);
                                } else {
                                    hidePoiLayer(which);
                                }
                                mPoiLayers.toggle(which);

                            }
                        });
                builder.setNeutralButton(R.string.close, new AlertDialogOnClickDismissHandler());
                builder.create().show();
            }
        });

        AutoCompleteTextView searchInput1 = (AutoCompleteTextView) findViewById(R.id.searchpanel_input);
        AutoCompleteTextView searchInput2 = (AutoCompleteTextView) findViewById(R.id.searchpanel_input_from);
        AutoCompleteTextView searchInput3 = (AutoCompleteTextView) findViewById(R.id.searchpanel_input_to);
        mSearchTexts = new AutoCompleteTextView[] { searchInput1, searchInput2, searchInput3 };
        View.OnFocusChangeListener searchInputFocusListener = new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus)
                    mSearchText = (AutoCompleteTextView) v;
            }
        };
        TextView.OnEditorActionListener searchInputActionListener = new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId != EditorInfo.IME_ACTION_SEARCH)
                    return false;

                if (mActiveSearchPanel == 0)
                    searchRoom(v.getText().toString());
                else
                    searchRoute(mSearchTexts[1].getText().toString(), mSearchTexts[2].getText().toString(),
                            mSearchMode);

                mSearchPanels[mActiveSearchPanel].setVisibility(View.GONE);

                // hide keyboard
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);

                return true;
            }
        };
        AdapterView.OnItemClickListener searchDropdownSelectionListener = new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                mSearchText.onEditorAction(mSearchText.getImeOptions());
            }
        };
        TextWatcher searchInputWatcher = new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                autocompleteRoomNameCancel();
                autocompleteRoomName(s.toString());
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        };
        for (AutoCompleteTextView si : mSearchTexts) {
            si.setOnFocusChangeListener(searchInputFocusListener);
            si.setOnEditorActionListener(searchInputActionListener);
            si.addTextChangedListener(searchInputWatcher);
            si.setOnItemClickListener(searchDropdownSelectionListener);
        }

        View searchPanel1 = findViewById(R.id.searchpanel);
        View searchPanel2 = findViewById(R.id.searchpanel2);
        mSearchPanels = new View[] { searchPanel1, searchPanel2 };

        findViewById(R.id.searchpanel_toggle_disability).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ImageButton ib = (ImageButton) v;
                mSearchMode = ib.getDrawable().getLevel() == SEARCHMODE_DEFAULT ? SEARCHMODE_ACCESSIBLE_WHEELCHAIR
                        : SEARCHMODE_DEFAULT;
                ib.setImageLevel(mSearchMode);
            }
        });

        // process search requests
        Uri data = getIntent().getData();
        if (data != null) {
            String url = data.toString();
            String room = url.substring(url.indexOf("?") + 1);
            mPendingSearch = true;
            mPendingSearchQuery = room;
        }

        loadMap();
    }

    private void loadMap() {
        progressNotificationOn();
        mWebView.loadUrl(MAP_URL);
    }

    private void showTimeoutMessage() {
        findViewById(R.id.timeout).setVisibility(View.VISIBLE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Analytics.onActivityStart(this, Analytics.ACTIVITY_MAP);
    }

    @Override
    protected void onStop() {
        super.onStop();
        Analytics.onActivityStop(this);
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        if (id == DIALOG_SEARCHING) {
            ProgressDialog d = new ProgressDialog(this);
            d.setIndeterminate(true);
            d.setCancelable(true);
            d.setMessage(getString(R.string.map_searching));
            return d;
        }
        return super.onCreateDialog(id);
    }

    @Override
    public void onBackPressed() {
        if (mSearchPanels[mActiveSearchPanel].getVisibility() == View.VISIBLE) {
            onSearchRequested();
        } else {
            super.onBackPressed();
        }
    }

    /**
     * Toggles the visibility of the active search panel
     */
    @Override
    public boolean onSearchRequested() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        View searchInput = null;

        if (mActiveSearchPanel == 0) {
            searchInput = mSearchTexts[0];
        } else {
            searchInput = mSearchTexts[1];
        }

        Animation slideInAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_in_top);
        Animation slideOutAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_out_top);

        final View panelToHideAfterAnimation = mSearchPanels[mActiveSearchPanel];
        slideOutAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation arg0) {
            }

            @Override
            public void onAnimationRepeat(Animation arg0) {
            }

            @Override
            public void onAnimationEnd(Animation arg0) {
                panelToHideAfterAnimation.setVisibility(View.GONE);
            }
        });

        if (mSearchPanels[mActiveSearchPanel].getVisibility() == View.GONE) {
            mSearchPanels[mActiveSearchPanel].setVisibility(View.VISIBLE);
            mSearchPanels[mActiveSearchPanel].startAnimation(slideInAnimation);
            searchInput.requestFocus();
            imm.showSoftInput(searchInput, InputMethodManager.SHOW_IMPLICIT);
        } else {
            mSearchPanels[mActiveSearchPanel].startAnimation(slideOutAnimation);
            //mSearchPanels[mActiveSearchPanel].setVisibility(View.GONE);
            imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0);
        }

        return true;
    }

    @Override
    public void progressNotificationOn() {
        findViewById(R.id.progress).setVisibility(View.VISIBLE);
    }

    @Override
    public void progressNotificationOff() {
        findViewById(R.id.progress).setVisibility(View.GONE);
    }

    private void getCampusLayers() {
        mWebView.loadUrl("javascript:getCampusLayers()");
    }

    private void showCampusLayer(int index) {
        mWebView.loadUrl("javascript:showCampusLayer(" + index + ")");
    }

    private void getPoiLayers() {
        mWebView.loadUrl("javascript:getPoiLayers()");
    }

    private void showPoiLayer(int index) {
        mWebView.loadUrl("javascript:showPoiLayer(" + index + ")");
    }

    private void hidePoiLayer(int index) {
        mWebView.loadUrl("javascript:hidePoiLayer(" + index + ")");
    }

    private void panTo(double lon, double lat) {
        mWebView.loadUrl("javascript:panTo(" + lon + "," + lat + ")");
    }

    private void zoomToUni() {
        mWebView.loadUrl("javascript:zoomToUni()");
    }

    private void searchRoom(String roomName) {
        if (isFinishing() || roomName == null || roomName.trim().length() == 0)
            return;
        roomName = roomName.trim();

        showDialog(DIALOG_SEARCHING);
        mWebView.loadUrl("javascript:searchRoom('" + roomName + "')");

        Log.d(TAG, "search room: " + roomName);
        Analytics.onEvent(Analytics.EVENT_MAP_SEARCH, "query", roomName);
    }

    private void searchRoute(String from, String to, int mode) {
        if (from == null || from.trim().length() == 0)
            return;
        from = from.trim();

        if (to == null || to.trim().length() == 0)
            return;
        to = to.trim();

        showDialog(DIALOG_SEARCHING);
        mWebView.loadUrl("javascript:searchRoute('" + from + "', '" + to + "', " + mode + ")");

        Log.d(TAG, "search route: " + from + " -> " + to + " (" + mode + ")");
        Analytics.onEvent(Analytics.EVENT_MAP_SEARCH, "queryFrom", from, "queryTo", to, "queryMode", mode + "");
    }

    private void autocompleteRoomName(String query) {
        mWebView.loadUrl("javascript:autocompleteRoomName('" + query + "')");
    }

    private void autocompleteRoomNameCancel() {
        mWebView.loadUrl("javascript:autocompleteRoomNameCancel()");
    }

    @SuppressWarnings("unused")
    private class SpJavaScriptInterface {

        SpJavaScriptInterface() {
        }

        @JavascriptInterface
        public void setCampusLayers(String jsonLayers) {
            List<Layer> layers = mGson.fromJson(jsonLayers, new TypeToken<List<Layer>>() {
            }.getType());
            for (Layer layer : layers) {
                Log.d(TAG, "Campus: " + layer);
            }
            mCampusLayers = new LayerManager(layers);
        }

        @JavascriptInterface
        public void setPoiLayers(String jsonLayers) {
            List<Layer> layers = mGson.fromJson(jsonLayers, new TypeToken<List<Layer>>() {
            }.getType());
            for (Layer layer : layers) {
                Log.d(TAG, "POI: " + layer);
            }
            mPoiLayers = new LayerManager(layers);
        }

        @JavascriptInterface
        public void log(String message) {
            Log.d(TAG, "SPI log: " + message);
        }

        @JavascriptInterface
        public void workFinished() {
            mHandler.post(new Runnable() {
                public void run() {
                    if (!isFinishing()) {
                        removeDialog(DIALOG_SEARCHING);
                    }
                }
            });
        }

        @JavascriptInterface
        public void reportCurrentBounds(double left, double top, double right, double bottom) {
            Log.d(TAG, "reportBounds: " + left + " " + top + " " + right + " " + bottom);
        }

        @JavascriptInterface
        public void roomNotFound(String room) {
            Toast.makeText(MapActivity.this, getString(R.string.map_error_roomnotfound, room), Toast.LENGTH_SHORT)
                    .show();
        }

        @JavascriptInterface
        public void reportRoomAutocompletion(String roomNames) {
            final List<String> roomNameList = mGson.fromJson(roomNames, new TypeToken<List<String>>() {
            }.getType());
            mHandler.post(new Runnable() { // update on GUI thread
                public void run() {
                    ArrayAdapter<String> a = new ArrayAdapter<String>(MapActivity.this,
                            android.R.layout.simple_dropdown_item_1line, roomNameList.toArray(new String[0]));
                    mSearchText.setAdapter(a);
                    a.notifyDataSetChanged(); // needed for the ac dropdown to be updated every time
                }
            });
        }
    }

    private static class Layer {

        public int index;
        public String name;

        public boolean visible;

        @Override
        public String toString() {
            return "Layer [index=" + index + ", name=" + name + "]";
        }
    }

    private static class LayerManager {

        private List<Layer> mLayers;

        public LayerManager(List<Layer> layers) {
            mLayers = layers;
        }

        public CharSequence[] getListItems() {
            CharSequence[] items = new CharSequence[mLayers.size()];
            for (Layer l : mLayers) {
                items[l.index] = l.name;
            }
            return items;
        }

        public int getListSingleSelectionIndex() {
            for (Layer l : mLayers) {
                if (l.visible)
                    return l.index;
            }
            return -1;
        }

        public boolean[] getListMultiSelection() {
            boolean selectedItems[] = new boolean[mLayers.size()];
            for (Layer l : mLayers) {
                if (l.visible)
                    selectedItems[l.index] = true;
            }
            return selectedItems;
        }

        public boolean isEmpty() {
            return mLayers.isEmpty();
        }

        public void select(int index) {
            mLayers.get(index).visible = true;
        }

        public void deselectAll() {
            for (Layer l : mLayers) {
                l.visible = false;
            }
        }

        public void toggle(int index) {
            mLayers.get(index).visible = !mLayers.get(index).visible;
        }

        public Layer get(int index) {
            return mLayers.get(index);
        }
    }
}