gt:         if (blacklist.isEmpty()) {
context: /*  Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
      Gobbetti, Normano64
  
      This file is part of Gadgetbridge.
  
      Gadgetbridge is free software: you can redistribute it and/or modify
      it under the terms of the GNU Affero General Public License as published
      by the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      Gadgetbridge is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU Affero General Public License for more details.
  
      You should have received a copy of the GNU Affero General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>. */
  package nodomain.freeyourgadget.gadgetbridge;
  
  import android.annotation.TargetApi;
  import android.app.Application;
  import android.app.NotificationManager;
  import android.app.NotificationManager.Policy;
  import android.content.Context;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.content.res.Resources;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
  import android.net.Uri;
  import android.os.Build;
  import android.os.Build.VERSION;
  import android.preference.PreferenceManager;
  import android.provider.ContactsContract.PhoneLookup;
  import android.support.v4.content.LocalBroadcastManager;
  import android.util.Log;
  import android.util.TypedValue;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.HashSet;
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  
  import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
  import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
  import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
  import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
  import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
  import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
  import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
  import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
  import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
  import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
  import nodomain.freeyourgadget.gadgetbridge.util.GB;
  import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
  import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
  import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
  
  /**
   * Main Application class that initializes and provides access to certain things like
   * logging and DB access.
   */
  public class GBApplication extends Application {
      // Since this class must not log to slf4j, we use plain android.util.Log
      private static final String TAG = "GBApplication";
      public static final String DATABASE_NAME = "Gadgetbridge";
  
      private static GBApplication context;
      private static final Lock dbLock = new ReentrantLock();
      private static DeviceService deviceService;
      private static SharedPreferences sharedPrefs;
      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
      private static final int CURRENT_PREFS_VERSION = 2;
      private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
      private static Prefs prefs;
      private static GBPrefs gbPrefs;
      private static LockHandler lockHandler;
      /**
       * Note: is null on Lollipop and Kitkat
       */
      private static NotificationManager notificationManager;
  
      public static final String ACTION_QUIT
              = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
      private static Logging logging = new Logging() {
          @Override
          protected String createLogDirectory() throws IOException {
              File dir = FileUtils.getExternalFilesDir();
              return dir.getAbsolutePath();
          }
      };
  
      private DeviceManager deviceManager;
  
      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();
      }
  
      public GBApplication() {
          context = this;
          // don't do anything here, add it to onCreate instead
      }
  
      protected DeviceService createDeviceService() {
          return new GBDeviceService(this);
      }
  
      @Override
      public void onCreate() {
          super.onCreate();
  
          if (lockHandler != null) {
              // guard against multiple invocations (robolectric)
              return;
          }
  
          sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
          prefs = new Prefs(sharedPrefs);
          gbPrefs = new GBPrefs(prefs);
  
          // don't do anything here before we set up logging, otherwise
          // slf4j may be implicitly initialized before we properly configured it.
          setupLogging(isFileLoggingEnabled());
  
          if (getPrefsFileVersion() != CURRENT_PREFS_VERSION) {
              migratePrefs(getPrefsFileVersion());
          }
  
          setupExceptionHandler();
  
          GB.environment = GBEnvironment.createDeviceEnvironment();
  
          setupDatabase(this);
  
          deviceManager = new DeviceManager(this);
  
          deviceService = createDeviceService();
          loadBlackList();
  
          if (isRunningMarshmallowOrLater()) {
              notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
          }
      }
  
      @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();
          for (GBDevice device : devices) {
              if (device.isBusy()) {
                  return true;
              }
          }
          return false;
      }
  
      public static void setupLogging(boolean enabled) {
          logging.setupLogging(enabled);
      }
  
      private void setupExceptionHandler() {
          LoggingExceptionHandler handler = new LoggingExceptionHandler(Thread.getDefaultUncaughtExceptionHandler());
          Thread.setDefaultUncaughtExceptionHandler(handler);
      }
  
      public static boolean isFileLoggingEnabled() {
          return prefs.getBoolean("log_to_file", false);
      }
  
      public static boolean minimizeNotification() {
          return prefs.getBoolean("minimize_priority", false);
      }
  
      static void setupDatabase(Context context) {
          DBOpenHelper helper = new DBOpenHelper(context, DATABASE_NAME, null);
          SQLiteDatabase db = helper.getWritableDatabase();
          DaoMaster daoMaster = new DaoMaster(db);
          if (lockHandler == null) {
              lockHandler = new LockHandler();
          }
          lockHandler.init(daoMaster, helper);
      }
  
      public static Context getContext() {
          return context;
      }
  
      /**
       * 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.
       */
      public static DeviceService deviceService() {
          return deviceService;
      }
  
      /**
       * 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!
       * <p>
       * Callers must not hold a reference to the returned instance because it
       * will be invalidated at some point.
       *
       * @return the DBHandler
       * @throws GBException
       * @see #releaseDB()
       */
      public static DBHandler acquireDB() throws GBException {
          try {
              if (dbLock.tryLock(30, TimeUnit.SECONDS)) {
                  return lockHandler;
              }
          } catch (InterruptedException ex) {
              Log.i(TAG, "Interrupted while waiting for DB lock");
          }
          throw new GBException("Unable to access the database.");
      }
  
      /**
       * Releases the database lock.
       *
       * @throws IllegalMonitorStateException if the current thread is not owning the lock
       * @see #acquireDB()
       */
      public static void releaseDB() {
          dbLock.unlock();
      }
  
      public static boolean isRunningLollipopOrLater() {
          return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
      }
  
      public static boolean isRunningMarshmallowOrLater() {
          return VERSION.SDK_INT >= Build.VERSION_CODES.M;
      }
  
      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 {
                  if (cursor != null && cursor.moveToFirst()) {
                      exists = true;
                      starred = cursor.getInt(cursor.getColumnIndexOrThrow(PhoneLookup.STARRED));
                  }
              } finally {
                  if (cursor != null) {
                      cursor.close();
                  }
              }
              if (prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS && exists) {
                  return true;
              } else if (prioritySenders == Policy.PRIORITY_SENDERS_STARRED && starred == 1) {
                  return true;
              }
              return false;
          }
      }
  
      @TargetApi(Build.VERSION_CODES.M)
      public static boolean isPriorityNumber(int priorityType, String number) {
          NotificationManager.Policy notificationPolicy = notificationManager.getNotificationPolicy();
          if (priorityType == Policy.PRIORITY_CATEGORY_MESSAGES) {
              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);
              }
          }
          return false;
      }
  
      @TargetApi(Build.VERSION_CODES.M)
      public static int getGrantedInterruptionFilter() {
          if (prefs.getBoolean("notification_filter", false) && GBApplication.isRunningMarshmallowOrLater()) {
              if (notificationManager.isNotificationPolicyAccessGranted()) {
                  return notificationManager.getCurrentInterruptionFilter();
              }
          }
          return NotificationManager.INTERRUPTION_FILTER_ALL;
      }
  
      public static HashSet<String> blacklist = null;
  
      private static void loadBlackList() {
          blacklist = (HashSet<String>) sharedPrefs.getStringSet("package_blacklist", null);
          if (blacklist == null) {
              blacklist = new HashSet<>();
          }
      }
  
      private static void saveBlackList() {
          SharedPreferences.Editor editor = sharedPrefs.edit();
  