2015-05-01 09:36:10 +02:00
package nodomain.freeyourgadget.gadgetbridge ;
import android.app.Application ;
2015-12-07 23:33:32 +01:00
import android.content.BroadcastReceiver ;
2015-05-01 09:36:10 +02:00
import android.content.Context ;
2015-12-07 23:33:32 +01:00
import android.content.Intent ;
import android.content.IntentFilter ;
2015-05-23 00:45:12 +02:00
import android.content.SharedPreferences ;
2015-06-06 00:10:38 +02:00
import android.os.Build ;
import android.os.Build.VERSION ;
2015-05-23 00:45:12 +02:00
import android.preference.PreferenceManager ;
2015-12-07 23:33:32 +01:00
import android.support.v4.content.LocalBroadcastManager ;
2015-06-21 10:18:41 +02:00
import android.util.Log ;
2015-05-23 00:45:12 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2015-05-01 09:36:10 +02:00
2015-05-13 23:15:20 +02:00
import java.io.File ;
2015-07-08 23:03:34 +02:00
import java.io.IOException ;
2015-09-25 00:53:40 +02:00
import java.util.HashSet ;
2015-08-03 01:17:02 +02:00
import java.util.concurrent.TimeUnit ;
import java.util.concurrent.locks.Lock ;
import java.util.concurrent.locks.ReentrantLock ;
2015-05-13 23:15:20 +02:00
2015-05-30 17:28:03 +02:00
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler ;
2015-12-08 23:42:58 +01:00
import nodomain.freeyourgadget.gadgetbridge.database.DBConstants ;
2015-08-03 01:17:02 +02:00
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler ;
2015-08-21 00:58:18 +02:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService ;
2016-02-02 17:33:24 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser ;
2015-08-21 00:58:18 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService ;
2015-07-08 23:03:34 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils ;
2015-08-22 01:08:46 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.GB ;
2016-01-09 16:07:22 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue ;
2015-05-30 17:28:03 +02:00
2015-12-23 22:15:50 +01:00
//import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
2015-10-26 23:32:03 +01:00
/ * *
* Main Application class that initializes and provides access to certain things like
* logging and DB access .
* /
2015-05-01 09:36:10 +02:00
public class GBApplication extends Application {
2015-08-03 01:17:02 +02:00
// Since this class must not log to slf4j, we use plain android.util.Log
private static final String TAG = " GBApplication " ;
2015-05-01 09:36:10 +02:00
private static GBApplication context ;
2015-05-30 17:28:03 +02:00
private static ActivityDatabaseHandler mActivityDatabaseHandler ;
2015-08-16 00:17:16 +02:00
private static final Lock dbLock = new ReentrantLock ( ) ;
2015-08-21 00:58:18 +02:00
private static DeviceService deviceService ;
2015-09-25 00:53:40 +02:00
private static SharedPreferences sharedPrefs ;
2016-02-02 17:33:24 +01:00
private static final String PREFS_VERSION = " shared_preferences_version " ;
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
2016-02-21 13:04:32 +01:00
private static final int CURRENT_PREFS_VERSION = 2 ;
2016-01-09 16:07:22 +01:00
private static LimitedQueue mIDSenderLookup = new LimitedQueue ( 16 ) ;
2015-05-01 09:36:10 +02:00
2015-12-07 23:33:32 +01:00
public static final String ACTION_QUIT
= " nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit " ;
private final BroadcastReceiver mReceiver = new BroadcastReceiver ( ) {
@Override
public void onReceive ( Context context , Intent intent ) {
String action = intent . getAction ( ) ;
switch ( action ) {
case ACTION_QUIT :
quit ( ) ;
break ;
}
}
} ;
private void quit ( ) {
GB . removeAllNotifications ( this ) ;
}
2015-05-01 09:36:10 +02:00
public GBApplication ( ) {
context = this ;
2015-07-25 00:07:33 +02:00
// don't do anything here, add it to onCreate instead
2015-05-01 09:36:10 +02:00
}
2015-08-21 00:58:18 +02:00
protected DeviceService createDeviceService ( ) {
2015-08-22 01:08:46 +02:00
return new GBDeviceService ( this ) ;
2015-08-21 00:58:18 +02:00
}
2015-05-13 23:15:20 +02:00
@Override
public void onCreate ( ) {
super . onCreate ( ) ;
2015-09-25 00:53:40 +02:00
sharedPrefs = PreferenceManager . getDefaultSharedPreferences ( context ) ;
2015-07-25 00:07:33 +02:00
// don't do anything here before we set up logging, otherwise
// slf4j may be implicitly initialized before we properly configured it.
2015-05-23 00:45:12 +02:00
setupLogging ( ) ;
2015-10-07 23:32:58 +02:00
2016-02-02 17:33:24 +01:00
if ( getPrefsFileVersion ( ) ! = CURRENT_PREFS_VERSION ) {
migratePrefs ( getPrefsFileVersion ( ) ) ;
}
2015-10-07 23:32:58 +02:00
setupExceptionHandler ( ) ;
2015-05-13 23:15:20 +02:00
// For debugging problems with the logback configuration
// LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// print logback's internal status
// StatusPrinter.print(lc);
// Logger logger = LoggerFactory.getLogger(GBApplication.class);
2015-06-05 21:46:56 +02:00
2015-10-03 23:26:36 +02:00
deviceService = createDeviceService ( ) ;
2015-08-22 01:08:46 +02:00
GB . environment = GBEnvironment . createDeviceEnvironment ( ) ;
2015-07-25 00:07:33 +02:00
mActivityDatabaseHandler = new ActivityDatabaseHandler ( context ) ;
2015-09-25 00:53:40 +02:00
loadBlackList ( ) ;
2015-12-07 23:33:32 +01:00
IntentFilter filterLocal = new IntentFilter ( ) ;
filterLocal . addAction ( ACTION_QUIT ) ;
LocalBroadcastManager . getInstance ( this ) . registerReceiver ( mReceiver , filterLocal ) ;
2015-06-05 21:46:56 +02:00
// for testing DB stuff
// SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase();
// db.close();
2015-05-13 23:15:20 +02:00
}
2015-10-07 23:32:58 +02:00
private void setupExceptionHandler ( ) {
LoggingExceptionHandler handler = new LoggingExceptionHandler ( Thread . getDefaultUncaughtExceptionHandler ( ) ) ;
Thread . setDefaultUncaughtExceptionHandler ( handler ) ;
}
2015-05-23 00:45:12 +02:00
public static boolean isFileLoggingEnabled ( ) {
2015-09-25 00:53:40 +02:00
return sharedPrefs . getBoolean ( " log_to_file " , false ) ;
2015-05-23 00:45:12 +02:00
}
private void setupLogging ( ) {
2015-06-12 22:30:14 +02:00
if ( isFileLoggingEnabled ( ) ) {
2015-07-08 23:03:34 +02:00
try {
File dir = FileUtils . getExternalFilesDir ( ) ;
2015-06-21 10:18:41 +02:00
// used by assets/logback.xml since the location cannot be statically determined
System . setProperty ( " GB_LOGFILES_DIR " , dir . getAbsolutePath ( ) ) ;
2015-10-03 22:30:11 +02:00
getLogger ( ) . info ( " Gadgetbridge version: " + BuildConfig . VERSION_NAME ) ;
2015-07-08 23:03:34 +02:00
} catch ( IOException ex ) {
2015-10-03 23:26:36 +02:00
Log . e ( " GBApplication " , " External files dir not available, cannot log to file " , ex ) ;
removeFileLogger ( ) ;
2015-06-12 22:30:14 +02:00
}
} else {
2015-10-03 23:26:36 +02:00
removeFileLogger ( ) ;
}
}
private void removeFileLogger ( ) {
try {
ch . qos . logback . classic . Logger root = ( ch . qos . logback . classic . Logger ) LoggerFactory . getLogger ( Logger . ROOT_LOGGER_NAME ) ;
root . detachAppender ( " FILE " ) ;
} catch ( Throwable ex ) {
Log . e ( " GBApplication " , " Error removing logger FILE appender " , ex ) ;
2015-05-13 23:15:20 +02:00
}
}
2015-10-03 22:30:11 +02:00
private Logger getLogger ( ) {
return LoggerFactory . getLogger ( GBApplication . class ) ;
}
2015-05-01 09:36:10 +02:00
public static Context getContext ( ) {
return context ;
}
2015-05-30 17:28:03 +02:00
2015-10-26 23:32:03 +01:00
/ * *
* Returns the facade for talking to devices . Devices are managed by
* an Android Service and this facade provides access to its functionality .
*
* @return the facade for talking to the service / devices .
* /
2015-08-21 00:58:18 +02:00
public static DeviceService deviceService ( ) {
return deviceService ;
}
2015-08-03 01:17:02 +02:00
/ * *
* Returns the DBHandler instance for reading / writing or throws GBException
* when that was not successful
* If acquiring was successful , callers must call # releaseDB when they
* are done ( from the same thread that acquired the lock !
2015-09-24 14:45:21 +02:00
*
2015-08-03 01:17:02 +02:00
* @return the DBHandler
* @throws GBException
2015-09-24 14:45:21 +02:00
* @see # releaseDB ( )
2015-08-03 01:17:02 +02:00
* /
public static DBHandler acquireDB ( ) throws GBException {
try {
if ( dbLock . tryLock ( 30 , TimeUnit . SECONDS ) ) {
return mActivityDatabaseHandler ;
}
} catch ( InterruptedException ex ) {
Log . i ( TAG , " Interrupted while waiting for DB lock " ) ;
}
throw new GBException ( " Unable to access the database. " ) ;
}
/ * *
* Releases the database lock .
2015-09-24 14:45:21 +02:00
*
2015-08-03 01:17:02 +02:00
* @throws IllegalMonitorStateException if the current thread is not owning the lock
* @see # acquireDB ( )
* /
public static void releaseDB ( ) {
dbLock . unlock ( ) ;
2015-05-30 17:28:03 +02:00
}
2016-04-14 16:15:58 +02:00
2015-06-06 00:10:38 +02:00
public static boolean isRunningLollipopOrLater ( ) {
return VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP ;
}
2015-09-25 00:53:40 +02:00
public static HashSet < String > blacklist = null ;
2015-10-26 23:32:03 +01:00
private static void loadBlackList ( ) {
2015-09-25 00:53:40 +02:00
blacklist = ( HashSet < String > ) sharedPrefs . getStringSet ( " package_blacklist " , null ) ;
if ( blacklist = = null ) {
blacklist = new HashSet < > ( ) ;
}
}
2015-10-26 23:32:03 +01:00
private static void saveBlackList ( ) {
2015-09-25 00:53:40 +02:00
SharedPreferences . Editor editor = sharedPrefs . edit ( ) ;
if ( blacklist . isEmpty ( ) ) {
editor . putStringSet ( " package_blacklist " , null ) ;
} else {
editor . putStringSet ( " package_blacklist " , blacklist ) ;
}
editor . apply ( ) ;
}
public static void addToBlacklist ( String packageName ) {
if ( ! blacklist . contains ( packageName ) ) {
blacklist . add ( packageName ) ;
saveBlackList ( ) ;
}
}
public static synchronized void removeFromBlacklist ( String packageName ) {
blacklist . remove ( packageName ) ;
saveBlackList ( ) ;
}
2015-12-08 23:42:58 +01:00
/ * *
* Deletes the entire Activity database and recreates it with empty tables .
2015-12-14 23:31:31 +01:00
*
2015-12-08 23:42:58 +01:00
* @return true on successful deletion
* /
public static synchronized boolean deleteActivityDatabase ( ) {
if ( mActivityDatabaseHandler ! = null ) {
mActivityDatabaseHandler . close ( ) ;
mActivityDatabaseHandler = null ;
}
boolean result = getContext ( ) . deleteDatabase ( DBConstants . DATABASE_NAME ) ;
mActivityDatabaseHandler = new ActivityDatabaseHandler ( getContext ( ) ) ;
return result ;
}
2015-12-14 23:31:31 +01:00
2016-02-02 17:33:24 +01:00
private int getPrefsFileVersion ( ) {
2016-02-21 13:04:32 +01:00
try {
return Integer . parseInt ( sharedPrefs . getString ( PREFS_VERSION , " 0 " ) ) ; //0 is legacy
} catch ( Exception e ) {
//in version 1 this was an int
return 1 ;
}
2016-02-02 17:33:24 +01:00
}
private void migratePrefs ( int oldVersion ) {
2016-02-21 13:04:32 +01:00
SharedPreferences . Editor editor = sharedPrefs . edit ( ) ;
2016-02-29 20:54:39 +01:00
switch ( oldVersion ) {
2016-02-02 17:33:24 +01:00
case 0 :
String legacyGender = sharedPrefs . getString ( " mi_user_gender " , null ) ;
String legacyHeight = sharedPrefs . getString ( " mi_user_height_cm " , null ) ;
String legacyWeigth = sharedPrefs . getString ( " mi_user_weight_kg " , null ) ;
2016-02-29 20:54:39 +01:00
String legacyYOB = sharedPrefs . getString ( " mi_user_year_of_birth " , null ) ;
if ( legacyGender ! = null ) {
2016-02-02 17:33:24 +01:00
int gender = " male " . equals ( legacyGender ) ? 1 : " female " . equals ( legacyGender ) ? 0 : 2 ;
2016-02-21 13:04:32 +01:00
editor . putString ( ActivityUser . PREF_USER_GENDER , Integer . toString ( gender ) ) ;
2016-02-02 17:33:24 +01:00
editor . remove ( " mi_user_gender " ) ;
}
2016-02-29 20:54:39 +01:00
if ( legacyHeight ! = null ) {
2016-02-02 17:33:24 +01:00
editor . putString ( ActivityUser . PREF_USER_HEIGHT_CM , legacyHeight ) ;
editor . remove ( " mi_user_height_cm " ) ;
}
2016-02-29 20:54:39 +01:00
if ( legacyWeigth ! = null ) {
2016-02-02 17:33:24 +01:00
editor . putString ( ActivityUser . PREF_USER_WEIGHT_KG , legacyWeigth ) ;
editor . remove ( " mi_user_weight_kg " ) ;
}
2016-02-29 20:54:39 +01:00
if ( legacyYOB ! = null ) {
2016-02-02 17:33:24 +01:00
editor . putString ( ActivityUser . PREF_USER_YEAR_OF_BIRTH , legacyYOB ) ;
editor . remove ( " mi_user_year_of_birth " ) ;
}
2016-02-21 13:04:32 +01:00
editor . putString ( PREFS_VERSION , Integer . toString ( CURRENT_PREFS_VERSION ) ) ;
2016-02-02 17:33:24 +01:00
break ;
2016-02-21 13:04:32 +01:00
case 1 :
2016-02-21 16:46:48 +01:00
//migrate the integer version of gender introduced in version 1 to a string value, needed for the way Android accesses the shared preferences
int legacyGender_1 = 2 ;
try {
legacyGender_1 = sharedPrefs . getInt ( ActivityUser . PREF_USER_GENDER , 2 ) ;
} catch ( Exception e ) {
Log . e ( TAG , " Could not access legacy activity gender " , e ) ;
2016-02-21 13:04:32 +01:00
}
2016-02-21 16:46:48 +01:00
editor . putString ( ActivityUser . PREF_USER_GENDER , Integer . toString ( legacyGender_1 ) ) ;
//also silently migrate the version to a string value
2016-02-21 13:04:32 +01:00
editor . putString ( PREFS_VERSION , Integer . toString ( CURRENT_PREFS_VERSION ) ) ;
break ;
2016-02-02 17:33:24 +01:00
}
2016-04-14 16:15:58 +02:00
editor . apply ( ) ;
2016-02-02 17:33:24 +01:00
}
2016-01-09 16:07:22 +01:00
public static LimitedQueue getIDSenderLookup ( ) {
2015-12-14 23:31:31 +01:00
return mIDSenderLookup ;
}
2016-04-14 15:21:25 +02:00
public static boolean isDarkThemeEnabled ( ) {
2016-04-14 16:15:58 +02:00
return sharedPrefs . getString ( " pref_key_theme " , context . getString ( R . string . pref_theme_value_light ) ) . equals ( context . getString ( R . string . pref_theme_value_dark ) ) ;
2016-04-14 15:21:25 +02:00
}
2015-05-01 09:36:10 +02:00
}