Java tutorial
/* Radiobeacon - Openbmap wifi and cell logger Copyright (C) 2013 wish7 This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.openbmap.activities; import java.io.IOException; import java.util.ArrayList; import org.mapsforge.core.model.BoundingBox; import org.mapsforge.core.model.LatLong; import org.mapsforge.map.android.graphics.AndroidGraphicFactory; import org.mapsforge.map.android.graphics.AndroidResourceBitmap; import org.mapsforge.map.android.rendertheme.AssetsRenderTheme; import org.mapsforge.map.android.util.AndroidUtil; import org.mapsforge.map.android.view.MapView; import org.mapsforge.map.layer.Layer; import org.mapsforge.map.layer.cache.TileCache; import org.mapsforge.map.layer.download.TileDownloadLayer; import org.mapsforge.map.layer.download.tilesource.OnlineTileSource; import org.mapsforge.map.layer.overlay.Marker; import org.mapsforge.map.model.common.Observer; import org.mapsforge.map.reader.MapFile; import org.mapsforge.map.rendertheme.XmlRenderTheme; import org.mapsforge.map.util.MapPositionUtil; import org.openbmap.R; import org.openbmap.RadioBeacon; import org.openbmap.db.DataHelper; import org.openbmap.db.RadioBeaconContentProvider; import org.openbmap.db.Schema; import org.openbmap.db.models.WifiRecord; import org.openbmap.heatmap.HeatLatLong; import org.openbmap.heatmap.HeatmapBuilder; import org.openbmap.heatmap.HeatmapBuilder.HeatmapBuilderListener; import org.openbmap.utils.MapUtils; import android.annotation.SuppressLint; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.Toast; /** * Fragment for displaying cell detail information */ public class WifiDetailsMap extends Fragment implements HeatmapBuilderListener, LoaderManager.LoaderCallbacks<Cursor> { private static final String TAG = WifiDetailsMap.class.getSimpleName(); /** * Radius heat-map circles */ private static final float RADIUS = 50f; private WifiRecord mWifi; // [start] UI controls /** * MapView */ private MapView mMapView; //[end] // [start] Map styles /** * Baselayer cache */ private TileCache mTileCache; /** * Online tile layer, used when no offline map available */ private static TileDownloadLayer mapDownloadLayer = null; //[end] // [start] Dynamic map variables private final ArrayList<HeatLatLong> points = new ArrayList<HeatLatLong>(); private boolean pointsLoaded = false; private boolean layoutInflated = false; private Observer mapObserver; /** * Current zoom level */ private byte currentZoom; private LatLong target; private boolean updatePending; private Marker heatmapLayer; private byte zoomAtTrigger; private AsyncTask<Object, Integer, Boolean> builder; // [end] @Override public final void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public final View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.wifidetailsmap, container, false); this.mMapView = (MapView) view.findViewById(R.id.map); return view; } @Override public final void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initMap(); mWifi = ((WifiDetailsActivity) getActivity()).getWifi(); if (savedInstanceState != null) { // reset loader after screen rotation // also see http://stackoverflow.com/questions/12009895/loader-restarts-on-orientation-change getActivity().getSupportLoaderManager().restartLoader(0, null, this); } else { getActivity().getSupportLoaderManager().initLoader(0, null, this); } } @Override public void onResume() { super.onResume(); if (mapDownloadLayer != null) { mapDownloadLayer.onResume(); } // register for zoom changes this.mapObserver = new Observer() { @Override public void onChange() { final byte zoom = mMapView.getModel().mapViewPosition.getZoomLevel(); if (zoom != currentZoom) { // update overlays on zoom level changed Log.i(TAG, "New zoom level " + zoom + ", reloading map objects"); /* // cancel pending heat-maps if (builder != null) { builder.cancel(true); } */ clearLayer(); proceedAfterHeatmapCompleted(); currentZoom = zoom; } } }; this.mMapView.getModel().mapViewPosition.addObserver(mapObserver); } @Override public void onDestroy() { super.onDestroy(); this.mTileCache.destroy(); this.mMapView.getModel().mapViewPosition.destroy(); this.mMapView.destroy(); AndroidResourceBitmap.clearResourceBitmaps(); } @Override public void onPause() { mMapView.getModel().mapViewPosition.removeObserver(mapObserver); if ((mapDownloadLayer != null)) { mapDownloadLayer.onResume(); } super.onPause(); } @Override public final Loader<Cursor> onCreateLoader(final int arg0, final Bundle arg1) { // set query params: bssid and session id final String[] args = { "-1", String.valueOf(RadioBeacon.SESSION_NOT_TRACKING) }; if (mWifi != null) { args[0] = mWifi.getBssid(); } final DataHelper dbHelper = new DataHelper(this.getActivity()); args[1] = String.valueOf(dbHelper.getActiveSessionId()); final String[] projection = { Schema.COL_ID, Schema.COL_SSID, Schema.COL_LEVEL, "begin_" + Schema.COL_LATITUDE, "begin_" + Schema.COL_LONGITUDE }; final CursorLoader cursorLoader = new CursorLoader(getActivity().getBaseContext(), RadioBeaconContentProvider.CONTENT_URI_WIFI_EXTENDED, projection, Schema.COL_BSSID + " = ? AND " + Schema.COL_SESSION_ID + " = ?", args, Schema.COL_LEVEL + " ASC"); return cursorLoader; } @Override public final void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) { if (cursor != null && cursor.getCount() > 0) { final int colLat = cursor.getColumnIndex("begin_" + Schema.COL_LATITUDE); final int colLon = cursor.getColumnIndex("begin_" + Schema.COL_LONGITUDE); final int colLevel = cursor.getColumnIndex(Schema.COL_LEVEL); while (cursor.moveToNext()) { //int intensity = (int) (cursor.getInt(colLevel) / -10); final int intensity = cursor.getInt(colLevel) / -50; points.add(new HeatLatLong(cursor.getDouble(colLat), cursor.getDouble(colLon), intensity)); } if (points.size() > 0) { mMapView.getModel().mapViewPosition.setCenter(points.get(points.size() - 1)); } pointsLoaded = true; proceedAfterHeatmapCompleted(); } } /** * */ private void proceedAfterHeatmapCompleted() { if (pointsLoaded && layoutInflated && !updatePending) { updatePending = true; clearLayer(); final BoundingBox bbox = MapPositionUtil.getBoundingBox( mMapView.getModel().mapViewPosition.getMapPosition(), mMapView.getDimension(), mMapView.getModel().displayModel.getTileSize()); target = mMapView.getModel().mapViewPosition.getCenter(); zoomAtTrigger = mMapView.getModel().mapViewPosition.getZoomLevel(); heatmapLayer = new Marker(target, null, 0, 0); mMapView.getLayerManager().getLayers().add(heatmapLayer); builder = new HeatmapBuilder(WifiDetailsMap.this, mMapView.getWidth(), mMapView.getHeight(), bbox, mMapView.getModel().mapViewPosition.getZoomLevel(), mMapView.getModel().displayModel.getScaleFactor(), mMapView.getModel().displayModel.getTileSize(), RADIUS).execute(points); } else { Log.i(TAG, "Another heat-map is currently generated. Skipped"); } } /* (non-Javadoc) * @see org.openbmap.soapclient.HeatmapBuilder.HeatmapBuilderListener#onHeatmapCompleted(android.graphics.Bitmap) */ @Override public final void onHeatmapCompleted(final Bitmap backbuffer) { // zoom level has changed in the mean time - regenerate if (mMapView.getModel().mapViewPosition.getZoomLevel() != zoomAtTrigger) { updatePending = false; Log.i(TAG, "Zoom level has changed - have to re-generate heat-map"); clearLayer(); proceedAfterHeatmapCompleted(); return; } final BitmapDrawable drawable = new BitmapDrawable(backbuffer); final org.mapsforge.core.graphics.Bitmap mfBitmap = AndroidGraphicFactory.convertToBitmap(drawable); if (heatmapLayer != null && mfBitmap != null) { heatmapLayer.setBitmap(mfBitmap); } else { Log.w(TAG, "Skipped heatmap draw: either layer or bitmap is null"); } updatePending = false; //saveHeatmapToFile(backbuffer); } /** * Callback function when heatmap generation has failed */ @Override public final void onHeatmapFailed() { updatePending = false; } /* (non-Javadoc) * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset(android.support.v4.content.Loader) */ @Override public void onLoaderReset(final Loader<Cursor> arg0) { } /** * Removes heatmap layer (if any) */ private void clearLayer() { if (heatmapLayer == null) { return; } if (mMapView.getLayerManager().getLayers().indexOf(heatmapLayer) != -1) { mMapView.getLayerManager().getLayers().remove(heatmapLayer); heatmapLayer = null; } } /** * Opens selected map file * @return a map file */ protected final MapFile getMapFile() { return MapUtils.getMapFile(getActivity()); } /** * Reads custom render theme from assets * @return render theme */ protected XmlRenderTheme getRenderTheme() { try { return new AssetsRenderTheme(this.getActivity(), "", "renderthemes/rendertheme-v4.xml"); } catch (final IOException e) { Log.e(TAG, "Render theme failure " + e.toString()); } return null; } /** * Initializes map components */ @SuppressLint("NewApi") private void initMap() { this.mTileCache = createTileCache(); if (MapUtils.isMapSelected(this.getActivity())) { final Layer offlineLayer = MapUtils.createTileRendererLayer(this.mTileCache, this.mMapView.getModel().mapViewPosition, getMapFile(), null, getRenderTheme()); if (offlineLayer != null) this.mMapView.getLayerManager().getLayers().add(offlineLayer); } else { //this.mMapView.getModel().displayModel.setBackgroundColor(0xffffffff); Toast.makeText(this.getActivity(), R.string.info_using_online_map, Toast.LENGTH_LONG).show(); final OnlineTileSource onlineTileSource = new OnlineTileSource( new String[] { "otile1.mqcdn.com", "otile2.mqcdn.com", "otile3.mqcdn.com", "otile4.mqcdn.com" }, 80); onlineTileSource.setName("MapQuest").setAlpha(false).setBaseUrl("/tiles/1.0.0/map/").setExtension("png") .setParallelRequestsLimit(8).setProtocol("http").setTileSize(256).setZoomLevelMax((byte) 18) .setZoomLevelMin((byte) 0); mapDownloadLayer = new TileDownloadLayer(mTileCache, mMapView.getModel().mapViewPosition, onlineTileSource, AndroidGraphicFactory.INSTANCE); mMapView.getLayerManager().getLayers().add(mapDownloadLayer); mapDownloadLayer.onResume(); } // register for layout finalization - we need this to get width and height final ViewTreeObserver vto = mMapView.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @SuppressLint("NewApi") @Override public void onGlobalLayout() { layoutInflated = true; proceedAfterHeatmapCompleted(); final ViewTreeObserver obs = mMapView.getViewTreeObserver(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { obs.removeOnGlobalLayoutListener(this); } else { obs.removeGlobalOnLayoutListener(this); } } }); // Register for zoom changes this.mapObserver = new Observer() { @Override public void onChange() { final byte newZoom = mMapView.getModel().mapViewPosition.getZoomLevel(); if (newZoom != currentZoom) { // update overlays on zoom level changed Log.i(TAG, "New zoom level " + newZoom + ", reloading map objects"); Log.i(TAG, "Update" + updatePending); // cancel pending heat-maps //if (builder != null) { // builder.cancel(true); //} // if another update is pending, wait for complete if (!updatePending) { clearLayer(); proceedAfterHeatmapCompleted(); } currentZoom = newZoom; } } }; this.mMapView.getModel().mapViewPosition.addObserver(mapObserver); this.mMapView.setClickable(true); this.mMapView.getMapScaleBar().setVisible(true); /* Layers layers = layerManager.getLayers(); // remove all layers including base layer layers.clear(); */ this.mMapView.getModel().mapViewPosition.setZoomLevel((byte) 16); } /** * Creates a separate tile cache * @return */ protected final TileCache createTileCache() { return AndroidUtil.createTileCache(this.getActivity(), "mapcache", mMapView.getModel().displayModel.getTileSize(), 1f, this.mMapView.getModel().frameBufferModel.getOverdrawFactor()); } /** * Saves heatmap to SD card * @param bitmap @SuppressLint("NewApi") private void saveHeatmapToFile(final Bitmap backbuffer) { try { FileOutputStream out = new FileOutputStream("/sdcard/result.png"); backbuffer.compress(Bitmap.CompressFormat.PNG, 90, out); out.close(); // rescan SD card on honeycomb devices // Otherwise files may not be visible when connected to desktop pc (MTP cache problem) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { Log.i(TAG, "Re-indexing SD card temp folder"); new MediaScanner(getActivity(), new File("/sdcard/")); } } catch (Exception e) { e.printStackTrace(); } } */ }