/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker 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.
Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.gps2.database.cache;
import java.io.File;
import java.util.ArrayList;
import rtree.AABB;
import rtree.BoundedObject;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.Video.VideoColumns;
import android.util.Log;
import com.rareventure.android.DbUtil;
import com.rareventure.android.Util;
import com.rareventure.android.database.Cache;
import com.rareventure.android.database.DbDatastoreAccessor;
import com.rareventure.android.database.TableInfo;
import com.rareventure.android.database.timmy.TimmyDatastoreAccessor;
import com.rareventure.android.encryption.EncryptedRow;
import com.rareventure.gps2.GTG;
import com.rareventure.gps2.MediaThumbnailCache;
import com.rareventure.gps2.reviewer.map.ViewMLT;
public class MediaLocTime extends EncryptedRow implements BoundedObject {
//x and y are absolute coordinates of the earth in ap panel format
public static final Column X = new Column("X", Integer.class);
public static final Column Y = new Column("Y", Integer.class);
//foreign key into android media table. This depends on what type of media file it is
public static final Column FK = new Column("FK",Integer.class);
//type of media... image, video, etc. and the temp loc bit indicates
//whether the x y position is temporary and might be changed if we get
//more gps positions
public static final Column FLAGS = new Column("FLAGS", Integer.class);
public static final Column TIME_SECS = new Column("TIME_SECS",Integer.class);
public static final int TYPE_IMAGE = 1;
public static final int TYPE_VIDEO = 2;
public static final int TEMP_LOC_BIT = 1<<16;
private static final int ORIENTATION_BIT_START_INDEX = 8;
private static final int TYPE_BIT_START_INDEX = 0;
//orientation is a 2 bit integer
public static final int ORIENTATION_BITS = 3<<ORIENTATION_BIT_START_INDEX;
private static final int TYPE_BITS = 255;
public static final Column[] COLUMNS = new Column[] { Y, X, FK, FLAGS, TIME_SECS};
public static final Preferences prefs = new Preferences();
public static final String TABLE_NAME = "media_loc_time";
public static final String INSERT_STATEMENT = DbDatastoreAccessor.createInsertStatement(TABLE_NAME);
public static final String UPDATE_STATEMENT = DbDatastoreAccessor.createUpdateStatement(TABLE_NAME);
public static final String DELETE_STATEMENT = DbDatastoreAccessor.createDeleteStatement(TABLE_NAME);
public static final TableInfo TABLE_INFO = new TableInfo(TABLE_NAME, COLUMNS, INSERT_STATEMENT, UPDATE_STATEMENT,
DELETE_STATEMENT);
/**
* size of data
*/
public static final int DATA_LENGTH =
EncryptedRow.figurePosAndSizeForColumns(COLUMNS);
//transient variables
//try to keep these at a minimum because all medialoctimes are loaded into memory
//used to position it in RTree
private AABB aabb;
//the current viewmlt associated with the mlt
public ViewMLT viewMlt;
/**
* This indicates the last time the mlt was checked to make
* sure the media still exists. The value is referenced against
* GTG.reviewerMapResumeId;
*/
public int cleanAsOfReviewerMapResumeId = Integer.MIN_VALUE;
public MediaLocTime() {
super();
}
public int getDataLength()
{
return DATA_LENGTH;
}
public int getX()
{
return getInt(X);
}
public int getY()
{
return getInt(Y);
}
public int getFk()
{
return getInt(FK);
}
public int getTimeSecs()
{
return getInt(TIME_SECS);
}
public int getType()
{
return getInt(FLAGS)&(TYPE_BITS);
}
public boolean isTempLoc()
{
return (getInt(FLAGS)&TEMP_LOC_BIT) != 0;
}
public void setData(int x, int y, int fk, int timeSecs, int type, boolean isTempLoc,
int orientation) {
data2 = new byte[DATA_LENGTH];
setInt(X.pos,x);
setInt(Y.pos,y);
setInt(FK.pos,fk);
setInt(TIME_SECS.pos,timeSecs);
setInt(FLAGS.pos,type | (isTempLoc ? TEMP_LOC_BIT : 0)
| convertOrientationToBits(orientation));
}
private int convertOrientationToBits(int orientation) {
return (((orientation%360)/90) >> ORIENTATION_BIT_START_INDEX);
}
private int convertOrientationBitsToValue(int flags) {
return (flags & ORIENTATION_BITS) << ORIENTATION_BIT_START_INDEX;
}
public int getOrientation()
{
return convertOrientationBitsToValue(getInt(FLAGS));
}
public String toStringFieldsOnly()
{
return
String.format("MediaLocTime(id=%d,x=%d,y=%d,fk=%d," +
"timeSecs=%d,tempLoc=%d,viewMlt=%s,isVideo=%s)",
this.id, getX(), getY(), getFk(),
getTimeSecs(), isTempLoc() ? 1 : 0, String.valueOf(viewMlt),
Boolean.toString(isVideo())
);
}
public String toString()
{
return toStringFieldsOnly();
}
public static class Preferences
{
}
@Override
public Cache getCache() {
return null;
}
public static ArrayList<MediaLocTime> loadAllMediaLocTime() {
TimmyDatastoreAccessor<MediaLocTime> da =
new TimmyDatastoreAccessor<MediaLocTime>(GTG.mediaLocTimeTimmyTable);
int maxSize = da.getNextRowId();
ArrayList<MediaLocTime> mltArray = new ArrayList<MediaLocTime>(maxSize);
for(int i = 0; i < maxSize; i++)
{
MediaLocTime row = new MediaLocTime();
try {
da.getRow(row, i);
}
catch(Exception e)
{
Log.e(GTG.TAG,"Corruption in media loc database? "+i);
//note that we don't need to do anything here, because
//when we insert new pictures, this row will assumed to be
//an empty deleted one and will be written over.
}
if(!row.isDeleted())
mltArray.add(row);
}
return mltArray;
}
@Override
public AABB getBounds() {
if(aabb == null)
{
aabb = new AABB();
aabb.setMinCorner(getX(), getY(), getTimeSecs());
aabb.setMaxCorner(getX()+1, getY()+1, getTimeSecs()+1);
}
return aabb;
}
public void setX(int x) {
setInt(X.pos, x);
aabb = null;
}
public void setY(int y) {
setInt(Y.pos, y);
aabb = null;
}
public void setIsTempLoc(boolean tempLoc) {
setInt(FLAGS.pos, (getInt(FLAGS) & (~TEMP_LOC_BIT)) | (tempLoc ? TEMP_LOC_BIT : 0));
}
public void setFk(int fk) {
setInt(FK.pos, fk);
}
public void setTimeSec(int timeSec) {
setInt(TIME_SECS.pos, timeSec);
aabb = null;
}
public void setType(int type)
{
setInt(FLAGS.pos, getInt(FLAGS) & (~TYPE_BITS) | ( type << TYPE_BIT_START_INDEX) );
}
// public static long getImageTime(String file) {
//
// }
/**
* Id used to store bitmaps in cache (incorporates video and image bit to keep
* them separate)
*
*/
public int getCacheId() {
if (getType() == MediaLocTime.TYPE_IMAGE) {
return getFk()
|MediaThumbnailCache.IMAGE_BIT;
}
if (getType() == MediaLocTime.TYPE_VIDEO) {
return getFk()
|MediaThumbnailCache.VIDEO_BIT;
}
throw new IllegalStateException();
}
public void setOrientation(int orientation) {
setInt(FLAGS.pos, getInt(FLAGS) & (~ORIENTATION_BITS) | convertOrientationToBits(orientation));
}
public Bitmap getActualBitmap(Context context) {
return Util.getBitmap(context, getFk(), getType() == TYPE_IMAGE);
}
public Bitmap getThumbnailBitmap(ContentResolver cr, boolean isMicroKind) {
if(isDeleted())
return null;
if(getType() == TYPE_IMAGE)
return MediaStore.Images.Thumbnails.getThumbnail(
cr,
getFk(),
isMicroKind ? MediaStore.Images.Thumbnails.MICRO_KIND :
MediaStore.Images.Thumbnails.MINI_KIND, null);
else
return MediaStore.Video.Thumbnails.getThumbnail(
cr,
getFk(),
isMicroKind ? MediaStore.Images.Thumbnails.MICRO_KIND :
MediaStore.Images.Thumbnails.MINI_KIND, null);
}
public String getFilename(ContentResolver cr)
{
Cursor cursor = null;
try {
if(getType() == TYPE_IMAGE)
{
String[] columns = new String[] {
ImageColumns.DATA, // filename
};
cursor = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
columns, ImageColumns._ID + " = ?",
new String[] { String.valueOf(getFk()) }, null);
}
else
{
String[] columns = new String[] {
VideoColumns.DATA, // filename
};
cursor = cr.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
columns, VideoColumns._ID + " = ?",
new String[] { String.valueOf(getFk()) }, null);
}
if(!cursor.moveToFirst())
return null;
return cursor.getString(0);
}
finally {
DbUtil.closeCursors(cursor);
}
}
public void markDeleted() {
setInt(FK.pos, -1);
}
public boolean isDeleted()
{
return getFk() == -1;
}
/**
*
* @return true if the media associated with the mlt is actually there
*/
public boolean isClean(Context context)
{
if(cleanAsOfReviewerMapResumeId == GTG.reviewerMapResumeId)
return true;
if(Util.mediaExists(context, getFk(), getType() == TYPE_IMAGE))
{
/* ttt_installer:remove_line */Log.d(GTG.TAG,"expensive isClean for "+id);
cleanAsOfReviewerMapResumeId = GTG.reviewerMapResumeId;
return true;
}
return false;
}
public boolean isVideo() {
return getType()==TYPE_VIDEO;
}
public Bitmap getLargeBitmap(int minSideLength, int maxNumberOfPixels,
ContentResolver cr) {
String filePath = Util.getDataFilepathForMedia(cr, getFk(), !isVideo());
if(filePath == null)
return null;
return com.rareventure.gps2.reviewer.imageviewer.Util.makeBitmap(minSideLength, maxNumberOfPixels,
Uri.fromFile(new File(filePath)), cr, true);
}
public Uri getUri(Context context) {
return Uri.fromFile(new File(Util.getDataFilepathForMedia(context.getContentResolver(),
getFk(), !isVideo())));
}
public String getMimeType(ContentResolver cr) {
return Util.getMimeTypeForMedia(cr,
getFk(), !isVideo());
}
}