Java tutorial
/** 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; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Map; import java.util.WeakHashMap; import org.acra.ACRA; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Environment; import android.os.StatFs; import android.preference.PreferenceManager; import android.support.v4.content.ContextCompat; import android.util.Log; import com.rareventure.android.AndroidPreferenceSet; import com.rareventure.android.AndroidPreferenceSet.AndroidPreferences; import com.rareventure.android.SuperThreadManager; import com.rareventure.android.Util; import com.rareventure.android.database.DbDatastoreAccessor; import com.rareventure.android.database.timmy.RollBackTimmyDatastoreAccessor; import com.rareventure.android.database.timmy.TimmyDatabase; import com.rareventure.android.database.timmy.TimmyTable; import com.rareventure.gps2.database.GpsLocationCache; import com.rareventure.gps2.database.GpsLocationRow; import com.rareventure.gps2.database.TAssert; import com.rareventure.gps2.database.TimeZoneTimeSet; import com.rareventure.gps2.database.UserLocationCache; import com.rareventure.gps2.database.cache.AreaPanel; import com.rareventure.gps2.database.cache.AreaPanelCache; import com.rareventure.gps2.database.cache.TimeTree; import com.rareventure.gps2.database.cache.TimeTreeCache; import com.rareventure.gps2.database.cachecreator.GpsTrailerCacheCreator; import com.rareventure.gps2.reviewer.map.MediaLocTimeMap; import com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity; import com.rareventure.util.BackgroundRunner; import com.rareventure.util.ReadWriteThreadManager; /** * Gps Trailer Globals... The reason we stick everything * here is 1) to make it easy to find, and 2) we might * have generic things like AndroidPreferenceSet which we * need to stick somewhere. 3) We have different apps using * the same globals but initialize them in different parts of * code */ public class GTG { //TODO 2 put autozoom and gps buttons behind location/time window //TODO 2 create blog and make a lot of interesting articles... get a following, and make an article on ttt, maybe // even what if you build it and they never come? //TODO 2.1 add wifi back //TODO 2.1 add magnetic readings //TODO 2.1 add visualization of altitude, magnetic readings, etc. //TODO 2 add file locations in manual //TODO 2 after premium is install and moved over, add a little dialog indicating that the trial version can be removed //TODO 2 add version number in about //TODO 2.1 sas isn't updated when cache is updated and it includes the latest time //TODO 2.1 when a line is really long and zoomed in really closely, antialiasing doesn't seem to work (at least on google nexus) //TODO 4 sometimes after taking pictures immediately, they refuse to show up in TTT. It seems that id's are being reused. They won't be reused unless the // *last* id was deleted. Maybe use date taken instead.. can't reproduce???? //TODO 2.5: work with phones without google maps? Look at ApiDemos manifest file for how to install without maps library //TODO 2.5: how to deal with errors because of device differences? //TODO 4: Maybe allow user to click on and label current position if available //TODO 2.5: highlight current position (we can do this by looking at last gps row, and if it was added within // a few minutes we'll consider it the current pos) //TODO 2.5 get rid of validation for restore gpx???? //TODO 2 where to put "(C) Rareventure LLC, All rights reserved"? In the manual, for instance? The about screen //TODO 2.5 make timeview oval drawer more accurate by drawing solid colors and empty spots //TODO 2.1 on most errors from cache, we should delete it as corrupted. This is incase there was some sort of powerfailure // which caused our code to fail, or the hardware failed. At least they can get into settings, probably and export the data //we should turn off cache creation in this case and notify user rather than just bombing //TODO 2.1 notify when time is not correct (ie points in the future) and that gps will be disabled until it is fixed //TODO 2.01 add pinch zooming and swipe to switch pictures in picture viewer maybe //TODO 2.5 rows created jsut as cache forms them are really big //TODO 3 make point location less "gridy" by looking at children locations //TODO 2.5 previous line from italy to argentina is being drawed for may 10th //TODO 2.5 when calculating paths, should not display any view nodes //TODO 2.5 maybe make the whole tree unknown when a path is calculated? //TODO 2.5 why doesn't the view nodes get recalcuated sometimes when a path is calcuated? //TODO 2.5 handle pictures wrt paths //TODO 2.5 put a number in areas //TODO 2.5 try not using wake lock during gps tracking //TODO 3 all videos sideloaded are at latest time //TODO 2.1 no tool tip for pressing on screen??? //TODO 2.5 get rid of number scroller in restore //TODO 2.1 canceling during timmy journal rollback causes freeze //TODO 3 collect gps data is slow when processing gps points are running //TODO 3 MyProgressDialog fails when flipping horizontally //TODO 2.1 either disable selecting areas or make them show distance (and maybe export gpx) //TODO 2.1 use bouncy castle for all, that way we are sure that it won't break when different // versions of java crypto are used //TODO 2.3 backup and restore. Backup and restore should use a common opensource format, maybe xml or something, or csv would be //the best I would think.It won't be password protected because that would be a pain for the user to enter a password // every single time they want to backup. Its up to the user to secure at that point. Then we could import and export this data at will. //TODO 2.5 daylight wasting time shows 1 am forever when zoomed far in in timeview. See Nov 6, 2011 1am //TODO 2.5 make sure we can turn on premium without a user having to pay... we'd do this with our own // store app //TODO 2.5 possibly use time encoded in video filename to determine date time of file. remove all letters and look // for dates beginning with 20xx (indicating a year) //TODO 2.1 make trial invoke regular app if both are installed, don't want both accessing the db at the same time, // note that this work has been started but not finished. The big task here is shutting down the db when // we detect the premium version is installed. We need to do two things: // 1. when trial is started and premium is installed, launch premium // 2. when premium is installed and trial is running, shutdown trial before loading premium // These must be done to avoid db cache corruption //TODO 2 Z make an explicit git version before release. Note the proguard mapping from class to obsfucated class // in proguard. Also store the whole translated tree, after ttt_install.pl, because the line numbers seem off //TODO 2.1 turn off get tasks permission //TODO 2 test everything: // battery low, // gps off, // sd card unmount, // ttt server down // select area // gps service alerts // different phones // backup //TODO 2.2 put toast when the current location isn't known and button is pressed //TODO 2.1 setup a very sparse AreaPanel cache tile of just the first few tiles // to get the user going //TODO 2.1 pinch zoom and swipe in photo viewer //TODO 3 try to speed up point loading //TODO 2.2 use barber pole when still thinking for point location bar in timeview //TODO 2.1 use speed and direction of movement for location center //TODO 3 solve the problem where for a time gap, there is a visible dot // at a large x/y area panel, and it disappears when we zoom in somehow //TODO 4 auto zoom when selecting time range by increasing size only, no panning????? //TODO 3 put a check in apcache to make sure that rwtm is working. Make it check //that anything accessing it while the writing thread is active is actually the //writing thread //TODO 2.2 prevent photos from changing size everytime we zoom in or zoom out //TODO 2.1 when going back to ttt, reset end time to latest gps time //TODO 4 only load points when plugged in into gtcache //TODO 2.1 option for darkening map so points stand out better? //TODO 2.1 facebook integration //TODO 4 slow down database point logging //TODO 2.1 bind to service so that we can notify it when // we are show location enabled //TODO 3 tool tip when "+ sas" button is checked //TODO 9 feedback $1 off //TODO 2.1 see about making red frog appear immediately when gps is turned off, or at least // when another reading is attempted (rather than when phone is turned on) //TODO 2.1 smoozy web page //TODO 2.5: what if timmy files are corrupted? //TODO 2.1: don't let screen go below south pole or north of north pole //TODO 2.1: maybe use startForeground() to show gps service notification rather than manually doing this //TODO 3: make landscape work decently //TODO 3: remove dbHack when we're sure that loading points and viewing them at the same // time works //TODO 3: remove log messages that appear way too much //TODO 2.5: encrypt background tiles (or have an option to do so) //TODO 2.1: make next/done button appear when entering passwords, etc. //TODO 2.5: make nicer frog?? //TODO 3: Link this app to facebook //TODO 2.5: tutorial //TODO 2.5: get rid of magnifying glass in autozoom button, because we aren't //exactly zooming //TODO 3: We should maybe have an option to save a limited set of data // (ie excluding AreaPanel et al) to save room. Then we could probably // send the data in an email for example. //TODO 3: add ability to merge an old backup with a current database // you will need to handle overlapping gps coords for this //TODO 2.5: add password timeout //TODO 2.5: wrap the world on the osm map view //TODO 2.5: allow option for password to be rechecked everytime the task is relaunched //TODO 2.5: make the gps service run when we want it to, immediately. Then we can // display the latest known point *and* it will go directly into the gps database //TODO 3: review code and clean up unnecessary classes/libs //TODO 3.5: histogram of speed to time spent at that speed //TODO 2.5: use proguard to optimize and obsfuscate code //TODO 2.5: possibly have ACRA display a troubleshooting link if things go wonky //TODO 2.5: check for whether there is GPS on phone //TODO 2.5: consider making a long standing background task for creating the database and setting up keys // while configuration is being done. If it's not finished by the time initial setup is done, then // put up a waiting dialog. //TODO 4: handle time range not changing for new GPS location row points while in map mode. (as long // as they're not in the app for days at a time, we'll never not be able to see new points the // way it's currently set up) //TODO 3: have a auto zoom time button? //TODO 2.5: split the database into a cache database and a normal database. To backup, we just save the //normal database //TODO 2.6: automatic backups //TODO 2.6: automatic backups and normal backups should prompt for passwords if there is no user password // (also have a "Change automatic password" box) //TODO 2.5: pan should wrap around in lon and hit the top and bottom of the earth in lat //TODO 2.1 turn this off if possible <uses-permission android:name="android.permission.GET_TASKS" /> public static final int IS_PREMIUM = /* ttt_installer:premium_neg42 */-42; //-42 is premium public static final String PREMIUM_APPLICATION_PACKAGE = /* ttt_installer:premium_package */"com.rareventure.gps2_foobar"; public static final String TRIAL_APPLICATION_PACKAGE = /* ttt_installer:trial_package */"com.rareventure.gps2_trial"; public static SQLiteDatabase db; /** * synchronization rules... only the gtgcachecreator can * perform transactions on the timmy db */ public static TimmyDatabase timmyDb; public static Intent BUY_PREMIUM_INTENT = new Intent(Intent.ACTION_VIEW); public static ReadWriteThreadManager initRwtm = new ReadWriteThreadManager(); /** * The timestamp of when the user last did an action where we would want them to * reenter their password normally. * * Used by password timeout functionality. */ public static long lastGtgClosedMS; //TODO 2.01 Z make video?? //TODO 2 Z make sure that market link works for BUY_PREMIUM_INTENT static { BUY_PREMIUM_INTENT.setData(Uri.parse("market://details?id=" + GTG.PREMIUM_APPLICATION_PACKAGE)); } /** * Requirements indicate system services and environment states that need to * be a certain way for an activity to run. * * Each requirement has an associated require...() method. These methods should * be run in the same order listed (but some may be skipped if unnecessary) * * The main reason we have requirements is that some activities require things that other * activities don't. We want to load in all the requirements for all the back activities * at once, so we have to OR requirements of all back pages together. This is opposed to * having simple "setup" methods to setup the system at once for different system modes. */ public static enum Requirement { INITIAL_SETUP, PREFS_LOADED(INITIAL_SETUP), NOT_IN_RESTORE( INITIAL_SETUP), NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE(PREFS_LOADED, INITIAL_SETUP), NOT_TRIAL_EXPIRED( PREFS_LOADED, INITIAL_SETUP), SDCARD_PRESENT(INITIAL_SETUP), /** * True if the system has been already installed (the external directory * created, the db created, initial paramaeters set, etc.) */ SYSTEM_INSTALLED(PREFS_LOADED, INITIAL_SETUP), DB_READY(PREFS_LOADED, INITIAL_SETUP, SDCARD_PRESENT), DECRYPT(SYSTEM_INSTALLED, DB_READY, PREFS_LOADED, INITIAL_SETUP), ENCRYPT(SYSTEM_INSTALLED, DB_READY, PREFS_LOADED, INITIAL_SETUP), /** * If this is not fulfilled, a password will be requested regardless if we need one to * decrypt or not. * This allows us to keep the system working for background processes (like * GpsTrailerCacheCreator) while still requesting a password if the user did * something that would normally lock the application. * * Note, be careful that you don't ask for this as a requirement, and later have the * user navigate to a screen that requires full decryption, because then the user * will have to enter their password again */ PASSWORD_ENTERED(PREFS_LOADED, INITIAL_SETUP), TIMMY_DB_READY(ENCRYPT, DECRYPT, PREFS_LOADED, INITIAL_SETUP), ; public int bit; public int requiredPriorRequirementsBitmap; private Requirement(Requirement... requiredPriorRequirements) { if (ordinal() > Integer.SIZE - 1) throw new IllegalStateException("too many states"); bit = 1 << ordinal(); for (Requirement r : requiredPriorRequirements) requiredPriorRequirementsBitmap = (requiredPriorRequirementsBitmap | r.bit | r.requiredPriorRequirementsBitmap); } /** * bitmap of all required prior requirements and current requirement */ public int priorAndCurrentBitmap() { return bit | requiredPriorRequirementsBitmap; } public void fulfill() { fulfilledRequirements = (fulfilledRequirements | bit); } public boolean isPriorRequirementsFulfilled() { return (requiredPriorRequirementsBitmap & (~fulfilledRequirements)) == 0; } public void assertPriorRequirementsFulfilled() { if (!isPriorRequirementsFulfilled()) { throw new IllegalStateException("Prior requirements not fulfilled, " + requiredPriorRequirementsBitmap + ", got " + fulfilledRequirements); } } public boolean isFulfilled() { return (fulfilledRequirements & bit) != 0; } public boolean isFulfilledAndAssertPriorRequirements() { // we make sure we are in write mode so the gps trailer service doesn't interfere // with the ui (since they both share the same application space and both // need to be initted) initRwtm.assertInWriteMode(); assertPriorRequirementsFulfilled(); return isFulfilled(); } public void reset() { fulfilledRequirements = (fulfilledRequirements & (~bit)); } public boolean isOn(int bitmap) { return (bitmap & bit) != 0; } } /** * When we just start out, none of the requirements are fulfilled */ public static int fulfilledRequirements = 0; /** * Initial setup should always be called first */ public static void requireInitialSetup(Context context, boolean inUi) { if (Requirement.INITIAL_SETUP.isFulfilledAndAssertPriorRequirements()) return; Requirement.INITIAL_SETUP.fulfill(); } public static boolean requireNotInRestore() { if (Requirement.NOT_IN_RESTORE.isFulfilledAndAssertPriorRequirements()) return true; if (GTG.GTGEvent.DOING_RESTORE.isOn) return false; Requirement.NOT_IN_RESTORE.fulfill(); return true; } public static void requirePrefsLoaded(Context context) { if (Requirement.PREFS_LOADED.isFulfilledAndAssertPriorRequirements()) return; loadPreferences(context); Requirement.PREFS_LOADED.fulfill(); } /** * @return true if not expired, false otherwise */ public static boolean requireNotTrialExpired() { if (Requirement.NOT_TRIAL_EXPIRED.isFulfilledAndAssertPriorRequirements()) return true; if (calcDaysBeforeTrialExpired() == 0) return false; Requirement.NOT_TRIAL_EXPIRED.fulfill(); return true; } /** * In the case where we're trial and premium package is installed * returns intent to go to premium package */ public static Intent requireNotTrialWhenPremiumIsAvailable(Context context) { if (Requirement.NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE.isFulfilledAndAssertPriorRequirements()) return null; //if trial if (GTG.IS_PREMIUM != -42) { Intent premiumStart = GTG.getGTGAppStart(context, GTG.PREMIUM_APPLICATION_PACKAGE); if (premiumStart != null) { return premiumStart; } } Requirement.NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE.fulfill(); return null; } public static boolean requireSdcardPresent(Context context) { if (Requirement.SDCARD_PRESENT.isFulfilledAndAssertPriorRequirements()) return true; externalFilesDir = context.getExternalFilesDir(null); if (!sdCardMounted(context)) { return false; } Requirement.SDCARD_PRESENT.fulfill(); return true; } public static boolean requireSystemInstalled(Context context) { if (Requirement.SYSTEM_INSTALLED.isFulfilledAndAssertPriorRequirements()) return true; if (!prefs.initialSetupCompleted) return false; Requirement.SYSTEM_INSTALLED.fulfill(); return true; } public static final int REQUIRE_DB_READY_OK = 0; public static final int REQUIRE_DB_READY_DB_DOESNT_EXIST = 1; /** * Opens the database. */ public static int requireDbReady() { //WARNING make sure to update RestoreGpxBackup if you add anything here if (Requirement.DB_READY.isFulfilledAndAssertPriorRequirements()) return REQUIRE_DB_READY_OK; File dbToUse = GpsTrailerDbProvider.getDbFile(false); if (!dbToUse.exists()) { return REQUIRE_DB_READY_DB_DOESNT_EXIST; } // If db is corrupt we simply throw an exception // and treat as an internal error. The reason being we are unsure if // an exception is a temporary occurrence or not. So if we prompt the // user to destroy an existing database but seemingly corrupt database // it may be a bad idea GTG.db = GpsTrailerDbProvider.openDatabase(dbToUse); Requirement.DB_READY.fulfill(); return REQUIRE_DB_READY_OK; } public static final int REQUIRE_DECRYPT_OK = 0; public static final int REQUIRE_DECRYPT_BAD_PASSWORD = 1; public static final int REQUIRE_DECRYPT_NEED_PASSWORD = 2; /** * Require both encrypt and decrypt requirements. * * @param password if there is a password, this must be set, otherwise may be null * @return */ public static int requireEncryptAndDecrypt(String password) { if (Requirement.DECRYPT.isFulfilledAndAssertPriorRequirements()) return REQUIRE_DECRYPT_OK; if (GpsTrailerCrypt.prefs.isNoPassword || password != null) { if (!GpsTrailerCrypt.initialize(GpsTrailerCrypt.prefs.isNoPassword ? null : password)) { return REQUIRE_DECRYPT_BAD_PASSWORD; } GTG.userLocationCache = new UserLocationCache(); GTG.gpsLocDbAccessor = new DbDatastoreAccessor<GpsLocationRow>(GpsLocationRow.TABLE_INFO); GTG.gpsLocCache = new GpsLocationCache(GTG.gpsLocDbAccessor, 10); GTG.tztSet = new TimeZoneTimeSet(); tztSet.loadSet(); } else return REQUIRE_DECRYPT_NEED_PASSWORD; Requirement.DECRYPT.fulfill(); Requirement.ENCRYPT.fulfill(); Requirement.PASSWORD_ENTERED.fulfill(); return REQUIRE_DECRYPT_OK; } public static void requireEncrypt() { if (Requirement.ENCRYPT.isFulfilledAndAssertPriorRequirements()) return; //TODO 4 we no longer need app id GpsTrailerCrypt.initializeWithoutPassword(MASTER_APP_ID); GTG.gpsLocDbAccessor = new DbDatastoreAccessor<GpsLocationRow>(GpsLocationRow.TABLE_INFO); GTG.gpsLocCache = new GpsLocationCache(GTG.gpsLocDbAccessor, 10); GTG.tztSet = new TimeZoneTimeSet(); //co: we can't load the set here because we lack the private key to decrypt it with //note that we could still decrypt this if there is no set password, but I'd rather //keep the flow the same regardless if the user set the password or not // tztSet.loadSet(); Requirement.ENCRYPT.fulfill(); return; } /** * Require that the password be entered since the last time the app was entered * or there is none. * * @param password if user entered a password, the password that was entered, otherwise * should be null * @param lastGtgClosedMS */ public static boolean requirePasswordEntered(String password, long lastGtgClosedMS) { if (Requirement.PASSWORD_ENTERED.isFulfilledAndAssertPriorRequirements()) return true; boolean status; if (GTG.prefs.passwordTimeoutMS != 0 && lastGtgClosedMS + GTG.prefs.passwordTimeoutMS > System.currentTimeMillis()) status = true; else if (password == null) status = GpsTrailerCrypt.prefs.isNoPassword; else status = GpsTrailerCrypt.verifyPassword(password); if (status) { Requirement.PASSWORD_ENTERED.fulfill(); return true; } return false; } public static int REQUIRE_TIMMY_DB_OK = 0; public static int REQUIRE_TIMMY_DB_IS_CORRUPT = 1; public static int REQUIRE_TIMMY_DB_NEEDS_UPGRADING = 2; public static int REQUIRE_TIMMY_DB_NEEDS_PROCESSING_TIME = 3; public static int requireTimmyDbReady(boolean canWaitAroundAwhile) { //WARNING!!! If you add anything here, make sure to update shutdownTimmyDb() if (Requirement.TIMMY_DB_READY.isFulfilledAndAssertPriorRequirements()) return REQUIRE_TIMMY_DB_OK; //now setup timmy database, (only for the ui) if (timmyDb == null) { try { GTG.timmyDb = GpsTrailerDbProvider.createTimmyDb(); } catch (IOException e) { Log.e(GTG.TAG, "Can't open timmy db", e); return REQUIRE_TIMMY_DB_IS_CORRUPT; } } //we check if timmy db is open outside of the timmyDb == null if // so that if we need more processing time, we don't need to null out timmy db // when we go back and try top open the db again if (!timmyDb.isOpen()) { try { if (timmyDb.isCorrupt()) { return REQUIRE_TIMMY_DB_IS_CORRUPT; } if (!canWaitAroundAwhile && timmyDb.needsProcessingTime()) return REQUIRE_TIMMY_DB_NEEDS_PROCESSING_TIME; timmyDb.open(); } catch (IOException e) { throw new IllegalStateException(e); } } //we need the database to be open before we mess with properties, so we //check if the timmy db needs upgrade outside of the open if. We also do this //because if the user cancels out of the upgrade screen, or hits the home key, //this method would be run again, so timmydb would already be open (and not upgraded) //which would be bad if we didn't know to upgrade. if (timmyDb.isNew()) { timmyDb.setProperty(CACHE_VERSION_NAME, String.valueOf(CACHE_VERSION)); } else if (!GTG.isTimmyDbLatestVersion()) return REQUIRE_TIMMY_DB_NEEDS_UPGRADING; if (apCache == null) { //note that we create these after open because they need to call // getNextRowId() which is only available after the database is opened. GTG.apCache = new AreaPanelCache(new RollBackTimmyDatastoreAccessor<AreaPanel>( GTG.timmyDb.getRollBackTable(GTG.getExternalStorageDirectory() + "/" + GpsTrailerDbProvider.APCACHE_TIMMY_TABLE_FILENAME))); GTG.ttCache = new TimeTreeCache(new RollBackTimmyDatastoreAccessor<TimeTree>( GTG.timmyDb.getRollBackTable(GTG.getExternalStorageDirectory() + "/" + GpsTrailerDbProvider.TIME_TREE_TIMMY_TABLE_FILENAME))); GTG.mediaLocTimeTimmyTable = GTG.timmyDb.getTable(GTG.getExternalStorageDirectory() + "/" + GpsTrailerDbProvider.MEDIA_LOC_TIME_TIMMY_TABLE_FILENAME); // GTG.mlcpCache = new MediaLocTimePlusCache( // new TimmyDatastoreAccessor<MediaLocTimePlus>( // GTG.timmyDb.getTable(context.getExternalFilesDir(null) + "/" // + MEDIA_LOC_TIME_PLUS_TIMMY_TABLE_FILENAME))); GTG.cacheCreator = new GpsTrailerCacheCreator(); GTG.mediaLocTimeMap = new MediaLocTimeMap(); GTG.mediaLocTimeMap.loadFromDb(); cacheCreator.start(); } Requirement.TIMMY_DB_READY.fulfill(); return REQUIRE_TIMMY_DB_OK; } public static AndroidPreferenceSet prefSet = new AndroidPreferenceSet() { @Override public void writePrefs(Map<String, Object> res) { res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.encryptedPrivateKey", GpsTrailerCrypt.prefs.encryptedPrivateKey); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.publicKey", GpsTrailerCrypt.prefs.publicKey); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.salt", GpsTrailerCrypt.prefs.salt); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.initialWorkPerformed", GpsTrailerCrypt.prefs.initialWorkPerformed); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.isNoPassword", GpsTrailerCrypt.prefs.isNoPassword); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.aesKeySize", GpsTrailerCrypt.prefs.aesKeySize); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.initialSetupCompleted", GTG.prefs.initialSetupCompleted); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.isCollectData", GTG.prefs.isCollectData); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.minBatteryPerc", GTG.prefs.minBatteryPerc); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.compassData", GTG.prefs.compassData); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.useMetric", GTG.prefs.useMetric); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.passwordTimeoutMS", GTG.prefs.passwordTimeoutMS); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.writeGpsWakeLockDebug", GTG.prefs.writeGpsWakeLockDebug); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.lastLon", OsmMapGpsTrailerReviewerMapActivity.prefs.lastLon); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.lastLat", OsmMapGpsTrailerReviewerMapActivity.prefs.lastLat); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.lastZoom", OsmMapGpsTrailerReviewerMapActivity.prefs.lastZoom); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.currTimePosSec", OsmMapGpsTrailerReviewerMapActivity.prefs.currTimePosSec); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.currTimePeriodSec", OsmMapGpsTrailerReviewerMapActivity.prefs.currTimePeriodSec); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.showPhotos", OsmMapGpsTrailerReviewerMapActivity.prefs.showPhotos); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.selectedColorRangesBitmap", OsmMapGpsTrailerReviewerMapActivity.prefs.selectedColorRangesBitmap); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.enableToolTips", OsmMapGpsTrailerReviewerMapActivity.prefs.enableToolTips); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.panelScale", OsmMapGpsTrailerReviewerMapActivity.prefs.panelScale); res.put(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerGpsStrategy.Preferences.batteryGpsOnTimePercentage", GpsTrailerGpsStrategy.prefs.batteryGpsOnTimePercentage); } @Override protected void loadPreference(String name, String value) { if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.encryptedPrivateKey")) GpsTrailerCrypt.prefs.encryptedPrivateKey = Util.toByte(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.publicKey")) GpsTrailerCrypt.prefs.publicKey = Util.toByte(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.salt")) GpsTrailerCrypt.prefs.salt = Util.toByte(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.initialWorkPerformed")) GpsTrailerCrypt.prefs.initialWorkPerformed = Boolean.parseBoolean(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.isNoPassword")) GpsTrailerCrypt.prefs.isNoPassword = Boolean.parseBoolean(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerCrypt.Preferences.aesKeySize")) GpsTrailerCrypt.prefs.aesKeySize = Integer.parseInt(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.initialSetupCompleted")) GTG.prefs.initialSetupCompleted = Boolean.parseBoolean(value); else if (name .equals(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.isCollectData")) GTG.prefs.isCollectData = Boolean.parseBoolean(value); else if (name .equals(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.minBatteryPerc")) GTG.prefs.minBatteryPerc = Float.parseFloat(value); else if (name .equals(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.compassData")) GTG.prefs.compassData = Integer.parseInt(value); else if (name.equals(/* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.useMetric")) GTG.prefs.useMetric = Boolean.parseBoolean(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.passwordTimeoutMS")) GTG.prefs.passwordTimeoutMS = Long.parseLong(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GTG.Preferences.writeGpsWakeLockDebug")) GTG.prefs.writeGpsWakeLockDebug = Boolean.parseBoolean(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.lastLon")) OsmMapGpsTrailerReviewerMapActivity.prefs.lastLon = Double.parseDouble(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.lastLat")) OsmMapGpsTrailerReviewerMapActivity.prefs.lastLat = Double.parseDouble(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.lastZoom")) OsmMapGpsTrailerReviewerMapActivity.prefs.lastZoom = Float.parseFloat(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.currTimePosSec")) OsmMapGpsTrailerReviewerMapActivity.prefs.currTimePosSec = Integer.parseInt(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.currTimePeriodSec")) OsmMapGpsTrailerReviewerMapActivity.prefs.currTimePeriodSec = Integer.parseInt(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.showPhotos")) OsmMapGpsTrailerReviewerMapActivity.prefs.showPhotos = Boolean.parseBoolean(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.selectedColorRangesBitmap")) OsmMapGpsTrailerReviewerMapActivity.prefs.selectedColorRangesBitmap = Integer.parseInt(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.enableToolTips")) OsmMapGpsTrailerReviewerMapActivity.prefs.enableToolTips = Boolean.parseBoolean(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.reviewer.map.OsmMapGpsTrailerReviewerMapActivity.Preferences.panelScale")) OsmMapGpsTrailerReviewerMapActivity.prefs.panelScale = Integer.parseInt(value); else if (name.equals( /* ttt_installer:obfuscate_str */"com.rareventure.gps2.GpsTrailerGpsStrategy.Preferences.batteryGpsOnTimePercentage")) GpsTrailerGpsStrategy.prefs.batteryGpsOnTimePercentage = Float.parseFloat(value); else Log.e(GTG.TAG, "ignoring pref: " + name); } }; public static GpsTrailerCrypt crypt; //WARNING if you add anoter cache type, be sure to update lockGpsCaches public static AreaPanelCache apCache; public static TimeTreeCache ttCache; public static GpsLocationCache gpsLocCache; public static TimeZoneTimeSet tztSet; public static UserLocationCache userLocationCache; /** * Use for debugging purposes only */ public static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hh:mm:ss"); public static final boolean CLEAR_OUT = false; public static final int HACK_FAIL_STOP = Integer.MAX_VALUE; public static final boolean DEBUG_SHOW_AREA_PANELS = false; public static final boolean COMMIT_TO_BEFORE_FAILURE = true; public static final boolean CHECK2 = false; public static final boolean FREE_VERSION = false; public static final boolean START_MAIN_APP = false; public static boolean HACK_MAKE_TT_CORRUPT = false; public static final boolean HACK_TURN_OFF_APCACHE_LOADING = false; public static boolean turnOnMethodTracing; public static Preferences prefs = new Preferences(); /** * A hack to communicate between activities */ public static GTGAction lastSuccessfulAction; public enum GTGAction { SET_FROM_AND_TO_DATES, TOOL_TIP_CLICKED }; /** * This lock is for the GpsTrailerCacheCreator. It prevents * (ui) threads that read the cache to find points from * interferring with writing to the cache. Since multiple threads * commonly access the cache at the same time, we don't use * regular synchronization, but instead this a ReadWriteThreadManager, * which allows multiple readers at the same time. */ public static ReadWriteThreadManager cacheCreatorLock = new ReadWriteThreadManager(); // public static BusyIndicationManager bim = new BusyIndicationManager(); /** * Named of default SharedPreferences object (persistent store) */ public static final String SHARED_PREFS_NAME = "GpsPrefs"; /** * ID for frog style notification pop up (for collecting data) */ public static final int FROG_NOTIFICATION_ID = -42; /** * Signifies the user_data_key row contains key of the master application (the one which knows the password) * All other keys will be removed and replaced by encrypting with this one. * (we have several keys because everytime we encrypt, we use a different key, signed by the master public key, * this is how we prevent the app from asking for a password on boot, yet still have everything encrypted) * */ public static final int MASTER_APP_ID = 0; public static final int SETTINGS_APP_ID = 3; public static final int GPS_TRAILER_SERVICE_APP_ID = 99; /** * True if we requested the user let us use gps, and they said no */ public static boolean userDoesntWantUsToHaveGpsPerm; //TODO 2.01 add instrumentation and automatic error reporting public static enum GTGEvent { ERROR_LOW_FREE_SPACE, ERROR_SDCARD_NOT_MOUNTED, ERROR_LOW_BATTERY, ERROR_GPS_DISABLED, ERROR_GPS_NO_PERMISSION, ERROR_SERVICE_INTERNAL_ERROR, TRIAL_PERIOD_EXPIRED, TTT_SERVER_DOWN, ERROR_UNLICENSED, PROCESSING_GPS_POINTS, LOADING_MEDIA, DOING_RESTORE; public boolean isOn; public Object obj; }; public static interface GTGEventListener { /** * Note that this will always be run in the UI thread * * @return true if the event has been handled and can be turned off. */ public boolean onGTGEvent(GTGEvent event); public void offGTGEvent(GTGEvent event); } public static long MIN_FREE_SPACE_ON_SDCARD = 50l * 1024 * 1024; //WARNING: this is hardcoded into R.string.error_low_free_space, //if updated, update there as well public static GTGEventListener MAIN_APP_GTG_EVENT_LISTENER = new GTGEventListener() { @Override public boolean onGTGEvent(GTGEvent event) { return false; } @Override public void offGTGEvent(GTGEvent event) { } }; /** * If there is a serious problem that would prevent the application from running * successfully, this will create an alert and return true. * Otherwise if things are good to go, it returns false */ public static boolean checkSdCard(Context context) { if (!sdCardMounted(context)) { GTG.alert(GTGEvent.ERROR_SDCARD_NOT_MOUNTED); return false; } StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); long sdAvailSize = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize(); if (sdAvailSize < MIN_FREE_SPACE_ON_SDCARD) { GTG.alert(GTGEvent.ERROR_LOW_FREE_SPACE); return false; } return true; } public static boolean sdCardMounted(Context context) { String state = Environment.getExternalStorageState(); //sometimes even though the external media is mounted, the external storage directory may not be present //for some reason return state.equals(Environment.MEDIA_MOUNTED) && (context == null || getExternalStorageDirectory() != null); } public static DbDatastoreAccessor<GpsLocationRow> gpsLocDbAccessor; public static SuperThreadManager superThreadManager = new SuperThreadManager(); /** * A background process for loading data into the areapanel and timetree * caches. We make it this way because it takes too long to commit * to the database to properly quit, so it often needs to run in the * background even when the user thinks they have exited the program * * Also handles populating mediaLocTimeMap and updating it to reflect * areapanels * * Synchronization rules: * any change to viewnodes, or areapanels must be synchronized against this * TODO 3 possibly change this to synchronize against a more appropriate * object, since cacheCreator is becoming a generic garbage man */ public static GpsTrailerCacheCreator cacheCreator; public static void notifyCollectDataServiceOfUpdate(Context context) { if (prefs.isCollectData) ContextCompat.startForegroundService(context, new Intent(context, GpsTrailerService.class)); else context.stopService(new Intent(context, GpsTrailerService.class)); } private static ArrayList<GTGEventListener> localEventListeners = new ArrayList<GTGEventListener>(); /** * Called when a major event has occurred, such as an internal error. * Is thread safe. Cannot be called before setupIfNecessary() is run. * <p> * This is used to notify the application of a specific condition * with the environment. Note that events are sticky * until handled. This can be used for two purposes, one is to * ignore an event until a screen comes up that can handle it. The * other is to display a constant message to the user and not * remove it until the condition is lifted (such as for low * free space). For the second case, the boolean isOn can * turn off an event. * <p> * Note that thread handling must be handled by the listener * * @param obj object to set into event. If set, alert will always run, otherwise * it will be ignored if the event is already on */ public static void alert(final GTGEvent event, final boolean isOn, final Object obj) { int i; // Log.d(GTG.TAG,"GTGAlert: event: "+event+", isOn: "+isOn); synchronized (eventListeners) { if (event.isOn == isOn && obj == null) { // Log.d(GTG.TAG,"GTGAlert: event ignored"); return; } event.isOn = isOn; event.obj = obj; localEventListeners.clear(); localEventListeners.addAll(eventListeners.keySet()); i = localEventListeners.size() - 1; // Log.d(GTG.TAG,"GTGAlert: num listeners "+(i+1)); } for (; i >= 0; i--) { GTGEventListener el; el = localEventListeners.get(i); if (event.isOn) { if (el.onGTGEvent(event)) { //if the event handler turned off the event, alert everyone that its off //and restart alert(event, false); break; } } else el.offGTGEvent(event); } //make sure to clear so that we don't have a memory leak (eventListeners is a WeakHashMap) localEventListeners.clear(); } public static void alert(GTGEvent event, boolean isOn) { alert(event, isOn, null); } public static void alert(final GTGEvent event) { alert(event, true, null); } public static enum SetupState { /** * A password is necessary to setup the database (returned * only if decryption is asked for) */ NEED_PASSWORD, /** * Success */ SUCCESS, /** * The database has not been initially setup. Note that this * will *not* be called if the system expects the database to * be present, but its not (ie. prefs.initialSetupCompleted * is true but the database isn't there). In this case, * a GTGEvent will be sent to gtgEventListener */ BEFORE_INITIAL_SETUP, BAD_PASSWORD, /** * The sdcard is not mounted, so we can't get the database */ SDCARD_NOT_MOUNTED, NEED_PROCESSING_TIME, TIMMY_DB_CORRUPT, TIMMY_DB_NEEDS_UPGRADE, /** * app is in trial mode and the premium version is installed */ IS_TRIAL_AND_PREMIUM_INSTALLED } public static boolean outsideApplication = false; private static final long TIME_TO_WAIT_BEFORE_CHECKING_ACTIVITY = 5000; /** * All intents must be under this package, or we won't be able to tell whether * the user is within the application or not. */ protected static final String GTG_PACKAGE_PREFIX = /* ttt_installer:obfuscate_str */"com.rareventure.gps2"; private static final String CACHE_VERSION_NAME = /* ttt_installer:obfuscate_str */"CACHE_VERSION"; private static final int CACHE_VERSION = 1; public static final String ENCODED_GOOGLE_PLAY_PUBLIC_KEY = /* ttt_installer:obfuscate_str */"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgDQPKbdJYuXJX4+RveoWle7vGL+YMm1tGjSa/rXtcDj04Su0QJCFaXF5I0cGGBQXwv7Y8lzFp/hsd34c8G0k9NyvPLacPVCLfBgWTt0WIINHKw8sWrwyDZO6Bph5V55My8J8Oi++ebkuSJFcEbSmblu/tI06CNmGd5uSBV2s8yVtv1eNjO+pQ3//ePONrKpehIASony9/gBQFT+vm3WYfNYIOyFsTP6f1mp5E+snNIdfp8H29jfxzNm1YwqQ2/AuFIMsXfzCmtD4zn/VWq5yaDlW2Rwh7pMNXs3FCthGFk88H9SUQew9ZReBHQaTl4uFUMlbJbP7l5oyEGGMg5Wv2QIDAQAB"; public static boolean isTimmyDbLatestVersion() { int version = timmyDb.getIntProperty(CACHE_VERSION_NAME, 0); return version == CACHE_VERSION; // && 1 == 0; //xODO 2 HACK } public static final String TAG = "GpsTrailer"; public static final int FILE_CACHE_TASK_PRIORITY = 5; public static final int REMOTE_LOADER_TASK_PRIORITY = 5; //low priority for this so it doesn't slow down the gui too much public static final int GPS_TRAILER_CACHE_CREATOR_PRIORITY = 1; public static final int GPS_TRAILER_OVERLAY_DRAWER_PRIORITY = 5; public static final int SELECTED_AREA_SET_PRIORITY = 5; public static final String APP_NAME = "Tiny Travel Tracker"; public static final int REQUIREMENTS_ENTER_PASSWORD = Requirement.INITIAL_SETUP.priorAndCurrentBitmap() | Requirement.PREFS_LOADED.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE.priorAndCurrentBitmap(); public static final int REQUIREMENTS_FATAL_ERROR = Requirement.INITIAL_SETUP.priorAndCurrentBitmap(); public static final int REQUIREMENTS_BASIC_UI = Requirement.INITIAL_SETUP.priorAndCurrentBitmap() | Requirement.NOT_IN_RESTORE.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_EXPIRED.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE.priorAndCurrentBitmap(); public static final int REQUIREMENTS_BASIC_PASSWORD_PROTECTED_UI = Requirement.INITIAL_SETUP .priorAndCurrentBitmap() | Requirement.NOT_IN_RESTORE.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_EXPIRED.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE.priorAndCurrentBitmap() | Requirement.DECRYPT.priorAndCurrentBitmap() | Requirement.ENCRYPT.priorAndCurrentBitmap() | Requirement.PASSWORD_ENTERED.priorAndCurrentBitmap(); public static final int REQUIREMENTS_DB_DOESNT_EXIST_PAGE = Requirement.INITIAL_SETUP.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_EXPIRED.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE.priorAndCurrentBitmap(); //co: although it's a little weird not to ask for a password here, we aren't allowing //the user any access to the gps points and if we do ask for a password and then //the user navigates to the main screen, they'd have to enter the password again // for the decrypt requirement // |Requirement.PASSWORD_ENTERED.priorAndCurrentBitmap(); public static final int REQUIREMENTS_FULL_PASSWORD_PROTECTED_UI = Requirement.DB_READY.priorAndCurrentBitmap() | Requirement.DECRYPT.priorAndCurrentBitmap() | Requirement.ENCRYPT.priorAndCurrentBitmap() | Requirement.INITIAL_SETUP.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_EXPIRED.priorAndCurrentBitmap() | Requirement.NOT_TRIAL_WHEN_PREMIUM_IS_AVAILABLE.priorAndCurrentBitmap() | Requirement.PASSWORD_ENTERED.priorAndCurrentBitmap() | Requirement.PREFS_LOADED.priorAndCurrentBitmap() | Requirement.SDCARD_PRESENT.priorAndCurrentBitmap() | Requirement.SYSTEM_INSTALLED.priorAndCurrentBitmap() | Requirement.NOT_IN_RESTORE.priorAndCurrentBitmap() | Requirement.TIMMY_DB_READY.priorAndCurrentBitmap(); public static final int REQUIREMENTS_TRIAL_EXPIRED_ACTIVITY = GTG.REQUIREMENTS_BASIC_PASSWORD_PROTECTED_UI & (~Requirement.NOT_TRIAL_EXPIRED.bit); /** * Used by create backup so that an expired trial can create a backup */ public static final int REQUIREMENTS_FULL_PASSWORD_PROTECTED_UI_EXPIRED_OK = REQUIREMENTS_FULL_PASSWORD_PROTECTED_UI & (~Requirement.NOT_TRIAL_EXPIRED.bit); /** * Initial setup wizard */ public static final int REQUIREMENTS_WIZARD = Requirement.INITIAL_SETUP.priorAndCurrentBitmap() | Requirement.PREFS_LOADED.priorAndCurrentBitmap(); public static final int REQUIREMENTS_RESTORE = REQUIREMENTS_FULL_PASSWORD_PROTECTED_UI & (~Requirement.NOT_IN_RESTORE.bit); public static final Class START_ACTIVITY_CLASS = OsmMapGpsTrailerReviewerMapActivity.class; //these are the preferences we save. private static void loadPreferences(Context context) { //TODO 3: notify user if an exception is thrown here when the // preferences are corrupted. We then should reset to factory settings... // don't forget to load the password check field GTG.prefSet.loadAndroidPreferences(context); //colorRanges only gets updated from this method (it is an array, so that's why it's // not stored in prefs directly) OsmMapGpsTrailerReviewerMapActivity.prefs .updateColorRangeBitmap(OsmMapGpsTrailerReviewerMapActivity.prefs.selectedColorRangesBitmap); } //note must be threadsafe for TTTClient //TODO 3 get rid of this method public static void savePreferences(Context context) { GTG.prefSet.savePrefs(context); } public static void runBackgroundTask(Runnable r) { if (backgroundRunner == null) { backgroundRunner = new BackgroundRunner(); backgroundRunner.setDaemon(true); backgroundRunner.start(); } backgroundRunner.addRunnable(r); } private static BackgroundRunner backgroundRunner; /** * * Synchronization rules: * any change to rTree or the mlts should synchronize against this */ public static MediaLocTimeMap mediaLocTimeMap; public static TimmyTable mediaLocTimeTimmyTable; /** * Everytime the reviewer map is resumed() this value is incremented */ public static int reviewerMapResumeId; private static Boolean isTrial; public static class Preferences implements AndroidPreferences { /** * Prompts the user to enter initial setup */ public boolean initialSetupCompleted = false; /** * If set, gps trailer service will run, otherwise no */ public boolean isCollectData; /** * Minimum amount of battery life before gps collection automatically shuts itself off */ public float minBatteryPerc = .35f; /** * True if we should use metric for scale and stuff */ public boolean useMetric; /** * Shhhh... this is really the date in seconds when the product was installed xor'ed with * a static value */ public int compassData = COMPASS_DATA_XOR; /** * If not zero, represents the amount of time before the password times out when * not inside the app */ public long passwordTimeoutMS; /** * If true will write to the gps wake lock debug file */ public boolean writeGpsWakeLockDebug = false; } public static int COMPASS_DATA_XOR = -1016932754; /** * Time for the trial to exist */ private static int TRIAL_TIME_LIMIT_SECS_XORED = Util.SECONDS_IN_MONTH ^ COMPASS_DATA_XOR; public static boolean isBillingSupported; /** * Sets up encryption for a new database, deleting all data * @param context * @param password if null, then will be setup with default "no password" password */ public static void setupCryptForNewDatabase(Context context, String password) { if (password == null) GpsTrailerCrypt.prefs.isNoPassword = true; else GpsTrailerCrypt.prefs.isNoPassword = false; GpsTrailerCrypt.deleteAllDataAndSetNewPassword(context, password); if (!setupCrypt(password)) { throw new IllegalStateException("What? created crypt with password and now it won't unlock"); } savePreferences(context); } /** * Does all the work for setuping encryption for the database */ private static boolean setupCrypt(String password) { if (!GpsTrailerCrypt.initialize(GpsTrailerCrypt.prefs.isNoPassword ? null : password)) { return false; } //WARNING!!! make sure to check that RestoreGpxBackup resets these things properly GTG.userLocationCache = new UserLocationCache(); GTG.gpsLocDbAccessor = new DbDatastoreAccessor<GpsLocationRow>(GpsLocationRow.TABLE_INFO); GTG.gpsLocCache = new GpsLocationCache(GTG.gpsLocDbAccessor, 10); GTG.tztSet = new TimeZoneTimeSet(); tztSet.loadSet(); return true; } /** * If expired, returns 0, otherwise the days left before expiration rounded up */ public static int calcDaysBeforeTrialExpired() { return 999999; // int currTimeSec = (int) (System.currentTimeMillis() / 1000l); // int startDateSec = prefs.compassData ^ COMPASS_DATA_XOR; // int timeRemaining = (GTG.TRIAL_TIME_LIMIT_SECS_XORED ^ COMPASS_DATA_XOR)- (currTimeSec - startDateSec); // // //is premium // if(IS_PREMIUM == -42) // return Integer.MAX_VALUE; // // //if start date was spoofed to future or trial is expired // if(startDateSec > currTimeSec || timeRemaining < 0) // return 0; // // return timeRemaining / Util.SECONDS_IN_DAY + 1; // } private static WeakHashMap<GTGEventListener, Object> eventListeners = new WeakHashMap<GTGEventListener, Object>(); static GpsTrailerService service; private static File externalFilesDir; public static void addGTGEventListener(GTGEventListener eventListener) { synchronized (eventListeners) { if (eventListeners.containsKey(eventListener)) return; for (GTGEvent event : GTGEvent.values()) { if (event.isOn) eventListener.onGTGEvent(event); } eventListeners.put(eventListener, Boolean.TRUE); } } public static void removeGTGEventListener(GTGEventListener eventListener) { synchronized (eventListeners) { eventListeners.remove(eventListener); } } public static File getExternalStorageDirectory() { return externalFilesDir; } private static boolean licenseCheckerBeingContacted; private static Object licenseCheckerBeingContactedLock = new Object(); public static Intent getGTGAppStart(Context context, String appPackage) { Intent i = new Intent(); i.setComponent(new ComponentName(appPackage, START_ACTIVITY_CLASS.getName())); //check if premium is installed if (Util.isCallable(context, i)) { return i; } return null; } /** * A hack to prevent gps and caches from being accessed when we are making * major changes. Locks all the caches in a newly created thread and doesn't * let go. In general if a change was made, killSelf() may * prove useful. */ public static void lockGpsCaches(final Runnable myRunnable) { final boolean[] ready = new boolean[1]; Runnable r = new Runnable() { @Override public void run() { synchronized (apCache) { synchronized (ttCache) { synchronized (gpsLocCache) { synchronized (this) { ready[0] = true; notify(); } if (myRunnable != null) myRunnable.run(); synchronized (this) { try { this.wait(); } catch (InterruptedException e) { } } } } } } }; new Thread(r).start(); synchronized (r) { while (!ready[0]) try { r.wait(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } } public static void setAppPasswordNotEntered() { if (Requirement.PASSWORD_ENTERED.isFulfilled()) GTG.lastGtgClosedMS = System.currentTimeMillis(); Requirement.PASSWORD_ENTERED.reset(); } /** * Creates an initializes a db (including creating a user data encrypting key. Crypt in preferences must be already set up. * Database is closed after being initialized */ public static void createAndInitializeNewDbFile() { //we create it as a temp file and then move it over so we are sure it //is initialized and ready before we start using it (in case we crash // part way through) File dbTmpFile = GpsTrailerDbProvider.getDbFile(true); SQLiteDatabase newDb = GpsTrailerDbProvider.createNewDbFile(dbTmpFile); GpsTrailerCrypt.generateAndInitializeNewUserDataEncryptingKey(GTG.MASTER_APP_ID, newDb); newDb.close(); dbTmpFile.renameTo(GpsTrailerDbProvider.getDbFile(false)); } /** * Should not be called when gps service is running */ public static void closeDbAndCrypt() { if (GTG.db != null) { GTG.db.close(); GTG.db = null; Requirement.DB_READY.reset(); } if (GTG.crypt != null) { GTG.crypt = null; Requirement.DECRYPT.reset(); Requirement.ENCRYPT.reset(); } } /** * Sets whether to enable acra or not. Changes and saves to storedpreferences */ public static void enableAcra(Context context, boolean checked) { //co: for use with ACRA 5+ (If I use it, it causes a problem acquiring wake locks) //SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sp = ACRA.getACRASharedPreferences(); Editor editor = sp.edit(); editor.putBoolean(ACRA.PREF_DISABLE_ACRA, !checked); if (!editor.commit()) TAssert.fail("failed storing to shared prefs"); } public static boolean isAcraEnabled(Context context) { //co: for use with ACRA 5+ (If I use it, it causes a problem acquiring wake locks) //SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sp = ACRA.getACRASharedPreferences(); return !sp.getBoolean(ACRA.PREF_DISABLE_ACRA, false); } public static void setIsInRestore(boolean b) { Requirement.NOT_IN_RESTORE.reset(); GTG.alert(GTGEvent.DOING_RESTORE, b); } private static Object shutdownTimmyDbLock = new Object(); private static Thread shutdownTimmyDbThread; /** * Note this may take awhile if the cache creator is busy. May be interrutped * with interruptShutdownTimmyDb(). */ public static void shutdownTimmyDb() { if (GTG.timmyDb == null) return; if (GTG.cacheCreator != null) { synchronized (shutdownTimmyDbLock) { shutdownTimmyDbThread = Thread.currentThread(); } GTG.cacheCreator.shutdown(); try { GTG.cacheCreator.join(); } catch (InterruptedException e) { return; } synchronized (shutdownTimmyDbLock) { shutdownTimmyDbThread = null; } GTG.cacheCreator = null; } GTG.apCache.clear(); GTG.apCache = null; GTG.ttCache.clear(); GTG.ttCache = null; GTG.mediaLocTimeMap = null; try { GTG.timmyDb.close(); } catch (IOException e) { throw new IllegalStateException(e); } GTG.timmyDb = null; GTG.mediaLocTimeTimmyTable = null; Requirement.TIMMY_DB_READY.reset(); } public static void interruptShutdownTimmyDb() { synchronized (shutdownTimmyDbLock) { if (shutdownTimmyDbThread != null) { shutdownTimmyDbThread.interrupt(); } } } }