2015-05-01 09:36:10 +02:00
package nodomain.freeyourgadget.gadgetbridge ;
2016-05-20 21:49:25 +02:00
import android.annotation.TargetApi ;
2015-05-01 09:36:10 +02:00
import android.app.Application ;
2016-05-19 23:58:13 +02:00
import android.app.NotificationManager ;
2016-05-19 16:34:59 +02:00
import android.app.NotificationManager.Policy ;
2015-05-01 09:36:10 +02:00
import android.content.Context ;
2015-12-07 23:33:32 +01:00
import android.content.Intent ;
2015-05-23 00:45:12 +02:00
import android.content.SharedPreferences ;
2016-04-23 23:24:56 +02:00
import android.content.res.Resources ;
2016-05-19 16:34:59 +02:00
import android.database.Cursor ;
2016-05-13 23:47:47 +02:00
import android.database.sqlite.SQLiteDatabase ;
2016-05-19 16:34:59 +02:00
import android.net.Uri ;
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 ;
2016-05-19 16:34:59 +02:00
import android.provider.ContactsContract.PhoneLookup ;
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 ;
2016-04-23 23:24:56 +02:00
import android.util.TypedValue ;
2015-05-23 00:45:12 +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 ;
2016-11-24 21:11:28 +01:00
import java.util.List ;
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-08-03 01:17:02 +02:00
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler ;
2016-08-27 22:51:00 +02:00
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper ;
2016-05-16 23:54:51 +02:00
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper ;
2016-06-18 23:35:34 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager ;
2016-04-17 19:52:51 +02:00
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster ;
2016-11-24 21:11:28 +01:00
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice ;
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-04-28 23:17:13 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs ;
2016-01-09 16:07:22 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue ;
2016-04-25 23:18:55 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.Prefs ;
2015-05-30 17:28:03 +02:00
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 " ;
2016-08-27 22:51:00 +02:00
public static final String DATABASE_NAME = " Gadgetbridge " ;
2015-05-01 09:36:10 +02:00
private static GBApplication context ;
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 ) ;
2016-04-25 23:18:55 +02:00
private static Prefs prefs ;
2016-04-28 23:17:13 +02:00
private static GBPrefs gbPrefs ;
2016-06-14 20:13:08 +02:00
private static LockHandler lockHandler ;
2016-05-20 22:04:30 +02:00
/ * *
* Note : is null on Lollipop and Kitkat
* /
2016-05-19 23:58:13 +02:00
private static NotificationManager notificationManager ;
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 " ;
2016-05-26 23:46:21 +02:00
private static Logging logging = new Logging ( ) {
@Override
protected String createLogDirectory ( ) throws IOException {
File dir = FileUtils . getExternalFilesDir ( ) ;
return dir . getAbsolutePath ( ) ;
}
} ;
2016-12-07 23:13:51 +01:00
private DeviceManager deviceManager ;
2015-12-07 23:33:32 +01:00
2016-07-08 22:35:52 +02:00
public static void quit ( ) {
GB . log ( " Quitting Gadgetbridge... " , GB . INFO , null ) ;
Intent quitIntent = new Intent ( GBApplication . ACTION_QUIT ) ;
LocalBroadcastManager . getInstance ( context ) . sendBroadcast ( quitIntent ) ;
GBApplication . deviceService ( ) . quit ( ) ;
2015-12-07 23:33:32 +01:00
}
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 ( ) ;
2016-08-25 00:00:53 +02:00
if ( lockHandler ! = null ) {
// guard against multiple invocations (robolectric)
return ;
}
2015-09-25 00:53:40 +02:00
sharedPrefs = PreferenceManager . getDefaultSharedPreferences ( context ) ;
2016-04-25 23:18:55 +02:00
prefs = new Prefs ( sharedPrefs ) ;
2016-04-28 23:17:13 +02:00
gbPrefs = new GBPrefs ( prefs ) ;
2015-09-25 00:53:40 +02:00
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.
2016-04-18 00:20:40 +02:00
setupLogging ( isFileLoggingEnabled ( ) ) ;
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-06-05 21:46:56 +02:00
2016-06-25 18:57:36 +02:00
GB . environment = GBEnvironment . createDeviceEnvironment ( ) ;
2016-05-17 00:51:00 +02:00
setupDatabase ( this ) ;
2016-04-17 19:52:51 +02:00
2016-06-18 23:35:34 +02:00
deviceManager = new DeviceManager ( this ) ;
2015-10-03 23:26:36 +02:00
deviceService = createDeviceService ( ) ;
2015-09-25 00:53:40 +02:00
loadBlackList ( ) ;
2015-12-07 23:33:32 +01:00
2016-05-19 23:58:13 +02:00
if ( isRunningMarshmallowOrLater ( ) ) {
2016-05-20 22:04:30 +02:00
notificationManager = ( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
2016-05-19 23:58:13 +02:00
}
2015-05-13 23:15:20 +02:00
}
2016-11-24 21:11:28 +01:00
@Override
public void onTrimMemory ( int level ) {
super . onTrimMemory ( level ) ;
if ( level > = TRIM_MEMORY_BACKGROUND ) {
if ( ! hasBusyDevice ( ) ) {
DBHelper . clearSession ( ) ;
}
}
}
/ * *
* Returns true if at least a single device is busy , e . g synchronizing activity data
* or something similar .
* Note : busy is not the same as connected or initialized !
* /
private boolean hasBusyDevice ( ) {
List < GBDevice > devices = getDeviceManager ( ) . getDevices ( ) ;
2016-12-07 23:13:51 +01:00
for ( GBDevice device : devices ) {
2016-11-24 21:11:28 +01:00
if ( device . isBusy ( ) ) {
return true ;
}
}
2016-11-25 14:53:12 +01:00
return false ;
2016-11-24 21:11:28 +01:00
}
2016-05-26 23:46:21 +02:00
public static void setupLogging ( boolean enabled ) {
logging . setupLogging ( enabled ) ;
}
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 ( ) {
2016-04-25 23:18:55 +02:00
return prefs . getBoolean ( " log_to_file " , false ) ;
2015-05-23 00:45:12 +02:00
}
2017-01-08 15:51:56 +01:00
public static boolean minimizeNotification ( ) {
return prefs . getBoolean ( " minimize_priority " , false ) ;
}
2016-05-17 00:51:00 +02:00
static void setupDatabase ( Context context ) {
2016-08-27 22:51:00 +02:00
DBOpenHelper helper = new DBOpenHelper ( context , DATABASE_NAME , null ) ;
2016-04-17 19:52:51 +02:00
SQLiteDatabase db = helper . getWritableDatabase ( ) ;
DaoMaster daoMaster = new DaoMaster ( db ) ;
2016-06-14 20:13:08 +02:00
if ( lockHandler = = null ) {
lockHandler = new LockHandler ( ) ;
}
lockHandler . init ( daoMaster , helper ) ;
2016-04-17 19:52:51 +02:00
}
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 !
2016-12-07 23:13:51 +01:00
* < p >
2016-06-14 20:13:08 +02:00
* Callers must not hold a reference to the returned instance because it
* will be invalidated at some point .
*
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 ) ) {
2016-05-16 23:00:04 +02:00
return lockHandler ;
2015-08-03 01:17:02 +02:00
}
} 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:44:44 +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
2016-05-19 16:34:59 +02:00
public static boolean isRunningMarshmallowOrLater ( ) {
return VERSION . SDK_INT > = Build . VERSION_CODES . M ;
}
2016-05-19 23:58:13 +02:00
private static boolean isPrioritySender ( int prioritySenders , String number ) {
if ( prioritySenders = = Policy . PRIORITY_SENDERS_ANY ) {
return true ;
} else {
Uri uri = Uri . withAppendedPath ( PhoneLookup . CONTENT_FILTER_URI , Uri . encode ( number ) ) ;
String [ ] projection = new String [ ] { PhoneLookup . _ID , PhoneLookup . STARRED } ;
Cursor cursor = context . getContentResolver ( ) . query ( uri , projection , null , null , null ) ;
boolean exists = false ;
int starred = 0 ;
try {
2016-05-20 22:04:30 +02:00
if ( cursor ! = null & & cursor . moveToFirst ( ) ) {
2016-05-19 23:58:13 +02:00
exists = true ;
starred = cursor . getInt ( cursor . getColumnIndexOrThrow ( PhoneLookup . STARRED ) ) ;
}
} finally {
if ( cursor ! = null ) {
cursor . close ( ) ;
}
2016-05-19 16:34:59 +02:00
}
2016-05-19 23:58:13 +02:00
if ( prioritySenders = = Policy . PRIORITY_SENDERS_CONTACTS & & exists ) {
return true ;
} else if ( prioritySenders = = Policy . PRIORITY_SENDERS_STARRED & & starred = = 1 ) {
return true ;
2016-05-19 16:34:59 +02:00
}
2016-05-19 23:58:13 +02:00
return false ;
2016-05-19 16:34:59 +02:00
}
2016-05-19 23:58:13 +02:00
}
2016-05-20 21:49:25 +02:00
@TargetApi ( Build . VERSION_CODES . M )
2016-05-19 23:58:13 +02:00
public static boolean isPriorityNumber ( int priorityType , String number ) {
NotificationManager . Policy notificationPolicy = notificationManager . getNotificationPolicy ( ) ;
2016-12-07 23:13:51 +01:00
if ( priorityType = = Policy . PRIORITY_CATEGORY_MESSAGES ) {
2016-05-19 23:58:13 +02:00
if ( ( notificationPolicy . priorityCategories & Policy . PRIORITY_CATEGORY_MESSAGES ) = = Policy . PRIORITY_CATEGORY_MESSAGES ) {
return isPrioritySender ( notificationPolicy . priorityMessageSenders , number ) ;
}
} else if ( priorityType = = Policy . PRIORITY_CATEGORY_CALLS ) {
if ( ( notificationPolicy . priorityCategories & Policy . PRIORITY_CATEGORY_CALLS ) = = Policy . PRIORITY_CATEGORY_CALLS ) {
return isPrioritySender ( notificationPolicy . priorityCallSenders , number ) ;
}
2016-05-19 16:34:59 +02:00
}
return false ;
}
2016-05-20 21:49:25 +02:00
@TargetApi ( Build . VERSION_CODES . M )
2016-05-19 23:58:13 +02:00
public static int getGrantedInterruptionFilter ( ) {
if ( prefs . getBoolean ( " notification_filter " , false ) & & GBApplication . isRunningMarshmallowOrLater ( ) ) {
if ( notificationManager . isNotificationPolicyAccessGranted ( ) ) {
return notificationManager . getCurrentInterruptionFilter ( ) ;
}
}
return NotificationManager . INTERRUPTION_FILTER_ALL ;
}
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
/ * *
2016-08-31 17:35:28 +02:00
* Deletes both the old Activity database and the new one 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
* /
2016-08-27 22:55:00 +02:00
public static synchronized boolean deleteActivityDatabase ( Context context ) {
2016-05-13 23:47:47 +02:00
// TODO: flush, close, reopen db
2016-05-17 00:51:00 +02:00
if ( lockHandler ! = null ) {
lockHandler . closeDb ( ) ;
}
2017-02-23 22:44:44 +01:00
boolean result = deleteOldActivityDatabase ( context ) ;
2016-08-27 22:51:00 +02:00
result & = getContext ( ) . deleteDatabase ( DATABASE_NAME ) ;
2015-12-08 23:42:58 +01:00
return result ;
}
2015-12-14 23:31:31 +01:00
2016-08-31 17:35:28 +02:00
/ * *
* Deletes the legacy ( pre 0 . 12 ) Activity database
*
* @return true on successful deletion
* /
public static synchronized boolean deleteOldActivityDatabase ( Context context ) {
DBHelper dbHelper = new DBHelper ( context ) ;
boolean result = true ;
2017-02-23 22:44:44 +01:00
if ( dbHelper . existsDB ( " ActivityDatabase " ) ) {
result = getContext ( ) . deleteDatabase ( " ActivityDatabase " ) ;
2016-08-31 17:35:28 +02:00
}
return result ;
}
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-25 23:18:55 +02:00
return prefs . 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
}
2016-04-23 23:24:56 +02:00
public static int getTextColor ( Context context ) {
TypedValue typedValue = new TypedValue ( ) ;
Resources . Theme theme = context . getTheme ( ) ;
theme . resolveAttribute ( android . R . attr . textColor , typedValue , true ) ;
return typedValue . data ;
}
2016-12-07 23:13:51 +01:00
2016-04-23 23:24:56 +02:00
public static int getBackgroundColor ( Context context ) {
TypedValue typedValue = new TypedValue ( ) ;
Resources . Theme theme = context . getTheme ( ) ;
theme . resolveAttribute ( android . R . attr . background , typedValue , true ) ;
return typedValue . data ;
}
2016-04-25 23:18:55 +02:00
public static Prefs getPrefs ( ) {
return prefs ;
}
2016-04-28 23:17:13 +02:00
public static GBPrefs getGBPrefs ( ) {
return gbPrefs ;
2016-04-14 15:21:25 +02:00
}
2016-06-18 23:35:34 +02:00
2016-12-07 23:13:51 +01:00
public DeviceManager getDeviceManager ( ) {
2016-06-18 23:35:34 +02:00
return deviceManager ;
}
2015-05-01 09:36:10 +02:00
}