com.google.samples.apps.iosched.map.MapFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.google.samples.apps.iosched.map.MapFragment.java

Source

/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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 com.google.samples.apps.iosched.map;

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.OnMapReadyCallback;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.IndoorBuilding;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;

import no.java.schedule.io.model.Coordinates;
import no.java.schedule.util.NetworkUtil;
import no.java.schedule.v2.R;
import no.java.schedule.util.EstimoteBeaconManager;

import com.google.android.gms.maps.model.MarkerOptions;
import com.google.samples.apps.iosched.map.util.MarkerLoadingTask;
import com.google.samples.apps.iosched.map.util.MarkerModel;
import com.google.samples.apps.iosched.provider.ScheduleContract;
import com.google.samples.apps.iosched.util.AnalyticsHelper;
import com.google.samples.apps.iosched.util.MapUtils;

import android.Manifest;
import android.app.Activity;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.DialogInterface;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;

/**
 * Shows a map of the conference venue.
 */
public class MapFragment extends com.google.android.gms.maps.MapFragment implements GoogleMap.OnMarkerClickListener,
        GoogleMap.OnIndoorStateChangeListener, GoogleMap.OnMapClickListener, OnMapReadyCallback {

    private static final LatLng OSOLOSPEKTRUM = new LatLng(59.9130, 10.7547);
    private static final int REQUEST_LOCATION = 0;

    private static final String EXTRAS_HIGHLIGHT_ROOM = "EXTRAS_HIGHLIGHT_ROOM";
    private static final String EXTRAS_ACTIVE_FLOOR = "EXTRAS_ACTIVE_FLOOR";

    private EstimoteBeaconManager mEstimoteBeaconManager;

    // Initial camera zoom
    private static final float CAMERA_ZOOM = 18;
    private static final float DETAILED_MAP_ZOOM_THRESHOLD = 17;
    private static final float CAMERA_BEARING = -27;
    private static Marker mCurrentLocationMarker = null;

    private static final int INVALID_FLOOR = Integer.MIN_VALUE;

    // Estimated number of floors used to initialise data structures with appropriate capacity
    private static final int INITIAL_FLOOR_COUNT = 3;

    // Default level (index of level in IndoorBuilding object for Oslo Spektrum)
    private static final int OSLOSPEKTRUM_DEFAULT_LEVEL_INDEX = 1;

    private static final String TAG = makeLogTag(MapFragment.class);

    // Markers stored by id
    private HashMap<String, MarkerModel> mMarkers = new HashMap<>();
    private ArrayList<GroundOverlay> mGroundOverlays = new ArrayList<>();
    // Markers stored by floor
    private SparseArray<ArrayList<Marker>> mFloorMarkerMapping = new SparseArray<>(INITIAL_FLOOR_COUNT);

    // Screen DPI
    private float mDPI = 0;

    private IndoorBuilding mOsloSpektrumBuilding = null;

    // currently displayed floor
    private int mFloor = INVALID_FLOOR;

    private Marker mActiveMarker = null;
    private BitmapDescriptor ICON_ACTIVE;
    private ArrayList<BitmapDescriptor> mFloorIcons = new ArrayList<>();

    private boolean mAtOsloSpektrum = false;
    private Marker mOsloSpektrumMarker = null;

    private GoogleMap mMap;
    private Rect mMapInsets = new Rect();

    private String mHighlightedRoomName = null;
    private MarkerModel mHighlightedRoom = null;
    private final static int mNumberFloors = 3;

    private int mInitialFloor = OSLOSPEKTRUM_DEFAULT_LEVEL_INDEX;

    private static final int TOKEN_LOADER_MARKERS = 0x1;
    //For Analytics tracking
    public static final String SCREEN_LABEL = "Map";

    public interface Callbacks {

        void onInfoHide();

        void onInfoShowOsloSpektrum();

        void onInfoShowTitle(String label, int roomType);

        void onInfoShowSessionlist(String roomId, String roomTitle, int roomType);

        void onInfoShowFirstSessionTitle(String roomId, String roomTitle, int roomType);
    }

    private static Callbacks sDummyCallbacks = new Callbacks() {

        @Override
        public void onInfoHide() {
        }

        @Override
        public void onInfoShowOsloSpektrum() {
        }

        @Override
        public void onInfoShowTitle(String label, int roomType) {
        }

        @Override
        public void onInfoShowSessionlist(String roomId, String roomTitle, int roomType) {
        }

        @Override
        public void onInfoShowFirstSessionTitle(String roomId, String roomTitle, int roomType) {
        }

    };

    private Callbacks mCallbacks = sDummyCallbacks;

    public static MapFragment newInstance() {
        return new MapFragment();
    }

    public static MapFragment newInstance(String highlightedRoomName) {
        MapFragment fragment = new MapFragment();
        Bundle arguments = new Bundle();
        arguments.putString(EXTRAS_HIGHLIGHT_ROOM, highlightedRoomName);
        fragment.setArguments(arguments);

        return fragment;
    }

    public static MapFragment newInstance(Bundle savedState) {
        MapFragment fragment = new MapFragment();
        fragment.setArguments(savedState);
        return fragment;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mActiveMarker != null) {
            // A marker is currently selected, restore its selection.
            outState.putString(EXTRAS_HIGHLIGHT_ROOM, mActiveMarker.getTitle());
            outState.putInt(EXTRAS_ACTIVE_FLOOR, INVALID_FLOOR);
        } else if (mAtOsloSpektrum) {
            // No marker is selected, store the active floor if at Oslo Spektrum.
            outState.putInt(EXTRAS_ACTIVE_FLOOR, mFloor);
            outState.putString(EXTRAS_HIGHLIGHT_ROOM, null);
        }

        LOGD(TAG, "Saved state: " + outState);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ActivityCompat.checkSelfPermission(getActivity(),
                    Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                    && ActivityCompat.checkSelfPermission(getActivity(),
                            Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(getActivity(), new String[] {
                        Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION },
                        REQUEST_LOCATION);
            }
        }

        // ANALYTICS SCREEN: View the Map screen
        // Contains: Nothing (Page name is a constant)
        AnalyticsHelper.sendScreenView(SCREEN_LABEL);

        // get DPI
        mDPI = getActivity().getResources().getDisplayMetrics().densityDpi / 160f;

        ICON_ACTIVE = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED);

        // Get the arguments and restore the highlighted room or displayed floor.
        Bundle data = getArguments();
        if (data != null) {
            mHighlightedRoomName = data.getString(EXTRAS_HIGHLIGHT_ROOM, null);
            mInitialFloor = data.getInt(EXTRAS_ACTIVE_FLOOR, OSLOSPEKTRUM_DEFAULT_LEVEL_INDEX);
        }

        getMapAsync(this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View mapView = super.onCreateView(inflater, container, savedInstanceState);
        mEstimoteBeaconManager = new EstimoteBeaconManager(getActivity(), this);
        mEstimoteBeaconManager.initializeEstimoteBeaconManager(getActivity());
        setMapInsets(mMapInsets);

        return mapView;
    }

    @Override
    public void onStart() {
        super.onStart();
        mEstimoteBeaconManager.startEstimoteBeaconManager();
    }

    @Override
    public void onResume() {
        super.onResume();

        if (!NetworkUtil.isGpsOn(getActivity())) {
            createGpsDialog().show();
        }
        if (!NetworkUtil.isBluetoothOn(getActivity())) {
            createBluetoothDialog().show();
        } else {
            mEstimoteBeaconManager.startMonitorEstimoteBeacons(getActivity());
        }

        if (mFloorIcons.isEmpty()) {
            for (int i = 0; i < mNumberFloors; i++) {
                mFloorIcons.add(BitmapDescriptorFactory.defaultMarker(MapUtils.createFloorColor(i)));
            }
        }
    }

    public void placeMarkerLocationOnCurrentRegion(Coordinates coordinates) {
        LatLng beaconRegionLatLng = new LatLng(coordinates.getLatitude(), coordinates.getLongitude());
        if (mCurrentLocationMarker != null) {
            mCurrentLocationMarker.remove();
            mCurrentLocationMarker = null;
        }

        MarkerOptions markerOptions = MapUtils.createCurrentLocationMarker("Your current location",
                beaconRegionLatLng);
        mCurrentLocationMarker = mMap.addMarker(markerOptions);
    }

    private AlertDialog.Builder createBluetoothDialog() {
        AlertDialog.Builder buildAlertDialog = new AlertDialog.Builder(getActivity(), R.style.Dialog_Theme);
        buildAlertDialog.setTitle("Bluetooth disabled");
        buildAlertDialog.setMessage("Please turn on Bluetooth to be able to use indoor maps");
        buildAlertDialog.setPositiveButton("Enable", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                NetworkUtil.enableBluetooth(getActivity());
                mEstimoteBeaconManager.startMonitorEstimoteBeacons(getActivity());
            }
        });
        buildAlertDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {

            }
        });

        return buildAlertDialog;
    }

    private AlertDialog.Builder createGpsDialog() {
        AlertDialog.Builder buildAlertDialog = new AlertDialog.Builder(getActivity(), R.style.Dialog_Theme);
        buildAlertDialog.setTitle("Location disabled");
        buildAlertDialog.setMessage("Please turn on Location to be able to use indoor maps");
        buildAlertDialog.setPositiveButton("Enable", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                NetworkUtil.enableGPS(getActivity());
                mEstimoteBeaconManager.startMonitorEstimoteBeacons(getActivity());
            }
        });
        buildAlertDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {

            }
        });

        return buildAlertDialog;
    }

    @Override
    public void onPause() {
        super.onPause();
        mEstimoteBeaconManager.stopRanging();
        mEstimoteBeaconManager.destroyEstimoteBeaconManager();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mEstimoteBeaconManager.destroyEstimoteBeaconManager();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == REQUEST_LOCATION) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED
                    && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                mMap.setMyLocationEnabled(true);
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    public void setMapInsets(int left, int top, int right, int bottom) {
        mMapInsets.set(left, top, right, bottom);
        if (mMap != null) {
            mMap.setPadding(mMapInsets.left, mMapInsets.top, mMapInsets.right, mMapInsets.bottom);
        }
    }

    public void setMapInsets(Rect insets) {
        mMapInsets.set(insets.left, insets.top, insets.right, insets.bottom);
        if (mMap != null) {
            mMap.setPadding(mMapInsets.left, mMapInsets.top, mMapInsets.right, mMapInsets.bottom);
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        mEstimoteBeaconManager.stopEstimoteBeaconManager();
    }

    /**
     * Clears the map and initialises all map variables that hold markers and overlays.
     */
    private void clearMap() {
        if (mMap != null) {
            mMap.clear();
        }

        mMarkers.clear();
        mFloorMarkerMapping.clear();

        mFloor = INVALID_FLOOR;
    }

    private void hideMarkersWhenSwitchingFloors() {
        setFloorElementsVisible(mFloor, false);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        mMap.setIndoorEnabled(true);
        mMap.setMyLocationEnabled(false);
        mMap.setOnMarkerClickListener(this);
        mMap.setOnIndoorStateChangeListener(this);
        mMap.setOnMapClickListener(this);

        mMap.setOnCameraIdleListener(new GoogleMap.OnCameraIdleListener() {
            @Override
            public void onCameraIdle() {
                if (mMap.getCameraPosition().zoom <= DETAILED_MAP_ZOOM_THRESHOLD) {
                    showMarkersForAllFloors(false);
                    mOsloSpektrumMarker.setVisible(true);
                } else {
                    setFloorElementsVisible(mFloor, true);
                    mOsloSpektrumMarker.setVisible(false);
                }
            }
        });

        UiSettings mapUiSettings = mMap.getUiSettings();
        mapUiSettings.setZoomControlsEnabled(false);
        mapUiSettings.setMapToolbarEnabled(false);

        // load all markers
        LoaderManager lm = getLoaderManager();
        lm.initLoader(TOKEN_LOADER_MARKERS, null, mMarkerLoader).forceLoad();

        setupMap(true);
    }

    public void showMarkersForAllFloors(boolean visible) {
        for (int i = 0; i < mNumberFloors; i++) {
            final ArrayList<Marker> markers = mFloorMarkerMapping.get(i);
            if (markers != null) {
                for (Marker m : markers) {
                    m.setVisible(visible);
                }
            }
        }
    }

    private void setupMap(boolean resetCamera) {
        showMarkersForAllFloors(true);
        mOsloSpektrumMarker = mMap.addMarker(MapUtils.createOsloSpektrumMarker(OSOLOSPEKTRUM).visible(false));

        if (resetCamera) {
            centerOnOsloSpektrum(false);
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (!(activity instanceof Callbacks)) {
            throw new ClassCastException("Activity must implement fragment's callbacks.");
        }

        mCallbacks = (Callbacks) activity;

        activity.getContentResolver().registerContentObserver(ScheduleContract.MapMarkers.CONTENT_URI, true,
                mObserver);
        activity.getContentResolver().registerContentObserver(ScheduleContract.MapTiles.CONTENT_URI, true,
                mObserver);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = sDummyCallbacks;

        getActivity().getContentResolver().unregisterContentObserver(mObserver);
    }

    private void centerOnOsloSpektrum(boolean animate) {
        CameraUpdate camera = CameraUpdateFactory.newCameraPosition(new CameraPosition.Builder()
                .bearing(CAMERA_BEARING).target(OSOLOSPEKTRUM).zoom(CAMERA_ZOOM).tilt(0f).build());
        if (animate) {
            mMap.animateCamera(camera);
        } else {
            mMap.moveCamera(camera);
        }
    }

    /**
     * Switches the displayed floor for which elements are displayed.
     * If the map is not initialised yet or no data has been loaded, nothing will be displayed.
     * If an invalid floor is specified and elements are currently on the map, all visible
     * elements will be hidden.
     * If this floor is not active for the indoor building, it is made active.
     *
     * @param floor index of the floor to display. It requires an overlay and at least one Marker
     *              to
     *              be defined for it and it has to be a valid index in the
     *              {@link com.google.android.gms.maps.model.IndoorBuilding} object that
     *              describes Oslo Spektrum.
     */
    private void showFloorElementsIndex(int floor) {
        LOGD(TAG, "Show floor " + floor);

        // Hide previous floor elements if the floor has changed
        if (mFloor != floor) {
            setFloorElementsVisible(mFloor, false);
        }

        mFloor = floor;

        if (mAtOsloSpektrum) {
            // Always hide the Oslo Spektrum marker if a floor is shown
            mOsloSpektrumMarker.setVisible(false);
            setFloorElementsVisible(mFloor, true);
        } else {
            // Show Oslo Spektrum marker if not at Oslo Spektrum or at an invalid floor
            mOsloSpektrumMarker.setVisible(true);
        }
    }

    /**
     * Change the active floor of Oslo Spektrum Center
     * to the given floor index. See {@link #showFloorElementsIndex(int)}.
     *
     * @param floor Index of the floor to show.
     * @see #showFloorElementsIndex(int)
     */
    private void showFloorIndex(int floor) {
        if (isValidFloor(floor) && mAtOsloSpektrum) {

            if (mMap.getFocusedBuilding().getActiveLevelIndex() == floor) {
                // This floor is already active, show its elements
                showFloorElementsIndex(floor);
            } else {
                // This floor is not shown yet, switch to this floor on the map
                mMap.getFocusedBuilding().getLevels().get(floor).activate();
            }

        } else {
            LOGD(TAG, "Can't show floor index " + floor + ".");
        }
    }

    /**
     * Change the visibility of all Markers and TileOverlays for a floor.
     */
    private void setFloorElementsVisible(int floor, boolean visible) {
        if (mFloor == INVALID_FLOOR) {
            showMarkersForAllFloors(true);
            return;
        }

        final ArrayList<Marker> markers = mFloorMarkerMapping.get(floor);
        if (markers != null) {
            for (Marker m : markers) {
                m.setVisible(visible);
            }
        }
    }

    private boolean isValidFloor(int floor) {
        return floor < mOsloSpektrumBuilding.getLevels().size();
    }

    private void enableMapElements() {
        if (mOsloSpektrumBuilding != null && mAtOsloSpektrum) {
            onIndoorLevelActivated(mOsloSpektrumBuilding);
        }
    }

    private void onDefocusOsloSpektrum() {
        // Hide all markers and tile overlays
        deselectActiveMarker();
        showFloorElementsIndex(INVALID_FLOOR);
        mCallbacks.onInfoShowOsloSpektrum();
    }

    private void onFocusHighlightedRoom() {
        // Highlight a room if argument is set and it exists, otherwise show the default floor
        if (mHighlightedRoomName != null && mMarkers.containsKey(mHighlightedRoomName)) {
            highlightRoom(mHighlightedRoomName);
            showMarkersForAllFloors(false);
            setFloorElementsVisible(mHighlightedRoom.floor, true);
            selectActiveMarker(mHighlightedRoom.marker);
            mCallbacks.onInfoShowFirstSessionTitle(mHighlightedRoom.id, mHighlightedRoom.label,
                    mHighlightedRoom.type);
            // Reset highlighted room because it has just been displayed.
            mHighlightedRoomName = null;
        }
    }

    @Override
    public void onIndoorBuildingFocused() {
        IndoorBuilding building = mMap.getFocusedBuilding();

        if (building != null && mOsloSpektrumBuilding == null
                && mMap.getProjection().getVisibleRegion().latLngBounds.contains(OSOLOSPEKTRUM)) {
            // Store the first active building. This will always be Oslo Spektrum
            mOsloSpektrumBuilding = building;
        }

        if (!mAtOsloSpektrum && building != null && building.equals(mOsloSpektrumBuilding)) {
            // Map is focused on Oslo Spektrum Center
            mAtOsloSpektrum = true;
            onFocusHighlightedRoom();
        } else if (mAtOsloSpektrum && mOsloSpektrumBuilding != null && !mOsloSpektrumBuilding.equals(building)) {
            // Map is no longer focused on Oslo Spektrum Center
            mAtOsloSpektrum = false;
            onDefocusOsloSpektrum();
        }
        onIndoorLevelActivated(building);
    }

    @Override
    public void onIndoorLevelActivated(IndoorBuilding indoorBuilding) {
        if (indoorBuilding != null && indoorBuilding.equals(mOsloSpektrumBuilding)) {
            onOsloSpektrumFloorActivated(indoorBuilding.getActiveLevelIndex());
        }
    }

    /**
     * Called when an indoor floor level in the Oslo Spektrum building has been activated.
     * If a room is to be highlighted, the map is centered and its marker is activated.
     */
    private void onOsloSpektrumFloorActivated(int activeLevelIndex) {
        if (mHighlightedRoom != null && mFloor == mHighlightedRoom.floor) {
            // A room highlight is pending. Highlight the marker and display info details.
            onMarkerClick(mHighlightedRoom.marker);
            centerMap(mHighlightedRoom.marker.getPosition());

            // Remove the highlight room flag, because the room has just been highlighted.
            mHighlightedRoom = null;
            mHighlightedRoomName = null;
        } else if (mFloor != activeLevelIndex) {
            // Deselect and hide the info details.
            deselectActiveMarker();
            mCallbacks.onInfoHide();
        }

        // Show map elements for this floor
        showFloorElementsIndex(activeLevelIndex);
    }

    @Override
    public void onMapClick(LatLng latLng) {
        deselectActiveMarker();
        mCallbacks.onInfoHide();
    }

    private void deselectActiveMarker() {
        if (mActiveMarker != null) {
            mActiveMarker.setVisible(false);
        }
    }

    private void selectActiveMarker(Marker marker) {
        if (marker != null) {
            mActiveMarker = mMap
                    .addMarker(MapUtils.createFloorMarkers("selected", mFloor, marker.getPosition()).visible(true));
            mActiveMarker.setIcon(ICON_ACTIVE);
        }
    }

    @Override
    public boolean onMarkerClick(Marker marker) {
        final String title = marker.getTitle();
        final MarkerModel model = mMarkers.get(title);

        AnalyticsHelper.sendEvent("Map", "markerclick", title);

        deselectActiveMarker();

        if (marker.equals(mCurrentLocationMarker)) {
            mCallbacks.onInfoShowTitle(marker.getTitle(), MarkerModel.TYPE_SESSION);
        }

        // The Oslo Spektrum marker can be compared directly.
        // For all other markers the model needs to be looked up first.
        if (marker.equals(mOsloSpektrumMarker)) {
            // Return camera to Oslo Spektrum
            LOGD(TAG, "Clicked on Oslo Spektrum marker, return to initial display.");
            centerOnOsloSpektrum(true);

        } else if (model != null && MapUtils.hasInfoTitleOnly(model.type)) {
            // Show a basic info window with a title only
            mCallbacks.onInfoShowTitle(model.label, model.type);
            selectActiveMarker(marker);

        } else if (model != null && MapUtils.hasInfoSessionList(model.type)) {
            // Type has sessions to display
            mCallbacks.onInfoShowSessionlist(model.id, model.label, model.type);
            selectActiveMarker(marker);

        } else if (model != null && MapUtils.hasInfoFirstDescriptionOnly(model.type)) {
            // Display the description of the first session only
            mCallbacks.onInfoShowFirstSessionTitle(model.id, model.label, model.type);
            selectActiveMarker(marker);

        } else {
            // Hide the bottom sheet for unknown markers
            mCallbacks.onInfoHide();
        }

        return true;
    }

    private void centerMap(LatLng position) {
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(position, CAMERA_ZOOM));
    }

    private void highlightRoom(String roomId) {
        MarkerModel m = mMarkers.get(roomId);
        if (m != null) {
            mHighlightedRoom = m;
        }
    }

    private void onMarkersLoaded(List<MarkerLoadingTask.MarkerEntry> list) {
        if (list != null) {
            for (MarkerLoadingTask.MarkerEntry entry : list) {

                // Skip incomplete entries
                if (entry.options == null || entry.model == null) {
                    break;
                }

                // Add marker to the map
                Marker m = mMap.addMarker(entry.options);
                MarkerModel model = entry.model;
                model.marker = m;

                // Store the marker and its model
                ArrayList<Marker> markerList = mFloorMarkerMapping.get(model.floor);
                if (markerList == null) {
                    // Initialise the list of Markers for this floor
                    markerList = new ArrayList<>();
                    mFloorMarkerMapping.put(model.floor, markerList);
                }
                markerList.add(m);
                mMarkers.put(model.id, model);
            }

            if (mFloor > INVALID_FLOOR) {
                setFloorElementsVisible(mFloor, true);
            }

            onFocusHighlightedRoom();

            if (mHighlightedRoomName == null) {
                ((MapActivity) getActivity()).mFloorAllButton.callOnClick();
            }

            LatLngBounds osloSpektrumLatLngBounds = new LatLngBounds(
                    new LatLng(59.91245775721807, 10.753627974205074), // South west corner
                    new LatLng(59.91353895983281, 10.755966466270479)); // North east corner

            GroundOverlayOptions osloSpektrumLevel0 = new GroundOverlayOptions()
                    .image(BitmapDescriptorFactory.fromResource(R.drawable.oslospektrum_level0))
                    .positionFromBounds(osloSpektrumLatLngBounds);

            GroundOverlayOptions osloSpektrumLevel1 = new GroundOverlayOptions()
                    .image(BitmapDescriptorFactory.fromResource(R.drawable.oslospektrum_level1))
                    .positionFromBounds(osloSpektrumLatLngBounds);

            GroundOverlayOptions osloSpektrumLevel2 = new GroundOverlayOptions()
                    .image(BitmapDescriptorFactory.fromResource(R.drawable.oslospektrum_level2))
                    .positionFromBounds(osloSpektrumLatLngBounds);

            mGroundOverlays.add(mMap.addGroundOverlay(osloSpektrumLevel0));
            mGroundOverlays.add(mMap.addGroundOverlay(osloSpektrumLevel1));
            mGroundOverlays.add(mMap.addGroundOverlay(osloSpektrumLevel2));

            mGroundOverlays.get(0).setVisible(true);
            for (int i = 1; i < mGroundOverlays.size(); i++) {
                mGroundOverlays.get(i).setVisible(false);
            }
        }

        enableMapElements();
    }

    public void showAllFloors(boolean visible) {
        mFloor = INVALID_FLOOR;
        showMarkersForAllFloors(visible);
    }

    public void showMarkersForSpecificFloor(int floorLevel) {
        if (mFloor == floorLevel) {
            return;
        }

        mFloor = floorLevel;

        if (mMap != null) {
            hideMarkersWhenSwitchingFloors();
        }

        mCallbacks.onInfoHide();
        deselectActiveMarker();
        setFloorElementsVisible(mFloor, true);

        showGroundOverlay(floorLevel);
    }

    public void showGroundOverlay(int floorLevel) {
        for (int i = 0; i < mGroundOverlays.size(); i++) {
            if (i == floorLevel) {
                mGroundOverlays.get(i).setVisible(true);
                continue;
            }

            mGroundOverlays.get(i).setVisible(false);
        }
    }

    private final ContentObserver mObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            if (!isAdded()) {
                return;
            }

            //clear map reload all data
            clearMap();
            setupMap(false);

            // reload data from loaders
            LoaderManager lm = getActivity().getLoaderManager();

            Loader<Cursor> loader = lm.getLoader(TOKEN_LOADER_MARKERS);
            if (loader != null) {
                loader.forceLoad();
            }
        }
    };

    /**
     * LoaderCallbacks for the {@link MarkerLoadingTask} that loads all markers for the map.
     */
    private LoaderCallbacks<List<MarkerLoadingTask.MarkerEntry>> mMarkerLoader = new LoaderCallbacks<List<MarkerLoadingTask.MarkerEntry>>() {
        @Override
        public Loader<List<MarkerLoadingTask.MarkerEntry>> onCreateLoader(int id, Bundle args) {
            return new MarkerLoadingTask(getActivity());
        }

        @Override
        public void onLoadFinished(Loader<List<MarkerLoadingTask.MarkerEntry>> loader,
                List<MarkerLoadingTask.MarkerEntry> data) {
            onMarkersLoaded(data);
        }

        @Override
        public void onLoaderReset(Loader<List<MarkerLoadingTask.MarkerEntry>> loader) {
        }
    };

}