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.db.DataHelper; import org.openbmap.db.RadioBeaconContentProvider; import org.openbmap.db.Schema; import org.openbmap.db.models.CellRecord; 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.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 CellDetailsMap extends Fragment implements HeatmapBuilderListener, LoaderManager.LoaderCallbacks<Cursor> { private static final String TAG = CellDetailsMap.class.getSimpleName(); /** * Radius heat-map circles */ private static final float RADIUS = 50f; private CellRecord mCell; // [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 mPointsLoaded = false; private boolean mLayoutInflated = false; private Observer mMapObserver; /** * Current zoom level */ private byte mCurrentZoom; private LatLong mTarget; private boolean mUpdatePending; private Marker mHeatmapLayer; private byte mZoomAtTrigger; // [end] @Override public final View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.celldetailsmap, container, false); this.mMapView = (MapView) view.findViewById(R.id.map); return view; } @Override public final void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); initMap(); mCell = ((CellDetailsActivity) getActivity()).getCell(); 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 final void onResume() { if (mapDownloadLayer != null) { mapDownloadLayer.onResume(); } } @Override public final void onPause() { if (mapDownloadLayer != null) { mapDownloadLayer.onResume(); } } @Override public void onDestroy() { super.onDestroy(); this.mTileCache.destroy(); this.mMapView.getModel().mapViewPosition.destroy(); this.mMapView.destroy(); AndroidResourceBitmap.clearResourceBitmaps(); } @Override public final Loader<Cursor> onCreateLoader(final int arg0, final Bundle arg1) { // set query params: id and session id final ArrayList<String> args = new ArrayList<String>(); String selectSql = ""; if (mCell != null && mCell.getLogicalCellId() != -1 && !mCell.isCdma()) { // typical gsm/hdspa cells selectSql = Schema.COL_LOGICAL_CELLID + " = ? AND " + Schema.COL_PSC + " = ?"; args.add(String.valueOf(mCell.getLogicalCellId())); args.add(String.valueOf(mCell.getPsc())); } else if (mCell != null && mCell.getLogicalCellId() == -1 && !mCell.isCdma()) { // umts cells selectSql = Schema.COL_PSC + " = ?"; args.add(String.valueOf(mCell.getPsc())); } else if (mCell != null && mCell.isCdma() && !mCell.getBaseId().equals("-1") && !mCell.getNetworkId().equals("-1") && !mCell.getSystemId().equals("-1")) { // cdma cells selectSql = Schema.COL_CDMA_BASEID + " = ? AND " + Schema.COL_CDMA_NETWORKID + " = ? AND " + Schema.COL_CDMA_SYSTEMID + " = ? AND " + Schema.COL_PSC + " = ?"; args.add(mCell.getBaseId()); args.add(mCell.getNetworkId()); args.add(mCell.getSystemId()); args.add(String.valueOf(mCell.getPsc())); } final DataHelper dbHelper = new DataHelper(this.getActivity()); args.add(String.valueOf(dbHelper.getActiveSessionId())); if (selectSql.length() > 0) { selectSql += " AND "; } selectSql += Schema.COL_SESSION_ID + " = ?"; final String[] projection = { Schema.COL_ID, Schema.COL_STRENGTHDBM, Schema.COL_TIMESTAMP, "begin_" + Schema.COL_LATITUDE, "begin_" + Schema.COL_LONGITUDE }; // query data from content provider final CursorLoader cursorLoader = new CursorLoader(getActivity().getBaseContext(), RadioBeaconContentProvider.CONTENT_URI_CELL_EXTENDED, projection, selectSql, args.toArray(new String[args.size()]), Schema.COL_STRENGTHDBM + " DESC"); 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_STRENGTHDBM); while (cursor.moveToNext()) { //int intensity = (int) (HEAT_AMPLIFIER * (Math.min(cursor.getInt(colLevel) + MIN_HEAT, 0)) / -10f); final int intensity = cursor.getInt(colLevel) / -1; points.add(new HeatLatLong(cursor.getDouble(colLat), cursor.getDouble(colLon), intensity)); } if (points.size() > 0) { mMapView.getModel().mapViewPosition.setCenter(points.get(points.size() - 1)); } mPointsLoaded = true; proceedAfterHeatmapCompleted(); // update host activity ((CellDetailsActivity) getActivity()).setNoMeasurements(cursor.getCount()); } } /** * */ private void proceedAfterHeatmapCompleted() { if (mPointsLoaded && mLayoutInflated && !mUpdatePending) { mUpdatePending = true; clearLayer(); final BoundingBox bbox = MapPositionUtil.getBoundingBox( mMapView.getModel().mapViewPosition.getMapPosition(), mMapView.getDimension(), mMapView.getModel().displayModel.getTileSize()); mTarget = mMapView.getModel().mapViewPosition.getCenter(); mZoomAtTrigger = mMapView.getModel().mapViewPosition.getZoomLevel(); mHeatmapLayer = new Marker(mTarget, null, 0, 0); mMapView.getLayerManager().getLayers().add(mHeatmapLayer); new HeatmapBuilder(CellDetailsMap.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() != mZoomAtTrigger) { mUpdatePending = 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 (mHeatmapLayer != null && mfBitmap != null) { mHeatmapLayer.setBitmap(mfBitmap); } else { Log.w(TAG, "Skipped heatmap draw: either layer or bitmap is null"); } mUpdatePending = false; //saveHeatmapToFile(backbuffer); } /** * Callback function when heatmap generation has failed */ @Override public final void onHeatmapFailed() { mUpdatePending = 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 (mHeatmapLayer == null) { return; } if (mMapView.getLayerManager().getLayers().indexOf(mHeatmapLayer) != -1) { mMapView.getLayerManager().getLayers().remove(mHeatmapLayer); mHeatmapLayer = null; } } /** * 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() { mLayoutInflated = 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.mMapObserver = new Observer() { @Override public void onChange() { final byte newZoom = mMapView.getModel().mapViewPosition.getZoomLevel(); if (newZoom != mCurrentZoom) { // update overlays on zoom level changed Log.i(TAG, "New zoom level " + newZoom + ", reloading map objects"); Log.i(TAG, "Update" + mUpdatePending); // cancel pending heat-maps //if (builder != null) { // builder.cancel(true); //} // if another update is pending, wait for complete if (!mUpdatePending) { clearLayer(); proceedAfterHeatmapCompleted(); } mCurrentZoom = newZoom; } } }; this.mMapView.getModel().mapViewPosition.addObserver(mMapObserver); this.mMapView.setClickable(true); this.mMapView.getMapScaleBar().setVisible(true); this.mMapView.getModel().mapViewPosition.setZoomLevel((byte) 16); } /** * Creates a separate map tile cache * @return */ protected final TileCache createTileCache() { return AndroidUtil.createTileCache(this.getActivity(), "mapcache", mMapView.getModel().displayModel.getTileSize(), 1f, this.mMapView.getModel().frameBufferModel.getOverdrawFactor()); } /** * Opens selected map file * @return a map file */ protected final MapFile getMapFile() { return MapUtils.getMapFile(getActivity()); } /** * 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(); } } */ }