gt:         if (apps == null)
context: /*
   * Copyright (C) 2010-12  Ciaran Gultnieks, ciaran@ciarang.com
   *
   * This program 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.
   *
   * This program 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 this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
   */
  
  package org.fdroid.fdroid;
  
  import java.io.File;
  import java.security.KeyManagementException;
  import java.security.KeyStore;
  import java.security.KeyStoreException;
  import java.security.NoSuchAlgorithmException;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.Semaphore;
  
  import javax.net.ssl.HttpsURLConnection;
  import javax.net.ssl.SSLContext;
  import javax.net.ssl.TrustManager;
  import javax.net.ssl.TrustManagerFactory;
  import javax.net.ssl.X509TrustManager;
  
  import android.app.Activity;
  import android.app.Application;
  import android.content.Context;
  import android.content.SharedPreferences;
  
  import org.fdroid.fdroid.Utils;
  
  import android.graphics.Bitmap;
  import android.preference.PreferenceManager;
  import android.util.Log;
  
  import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache;
  import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
  import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
  import com.nostra13.universalimageloader.core.DisplayImageOptions;
  import com.nostra13.universalimageloader.core.ImageLoader;
  import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
  import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
  import com.nostra13.universalimageloader.utils.StorageUtils;
  
  import de.duenndns.ssl.MemorizingTrustManager;
  
  import org.thoughtcrime.ssl.pinning.PinningTrustManager;
  import org.thoughtcrime.ssl.pinning.SystemKeyStore;
  
  public class FDroidApp extends Application {
  
      private static enum Theme {
          dark, light
      }
      private static Theme curTheme = Theme.dark;
  
      public void reloadTheme() {
          curTheme = Theme.valueOf(PreferenceManager
                  .getDefaultSharedPreferences(getBaseContext())
                  .getString(Preferences.PREF_THEME, "dark"));
      }
      public void applyTheme(Activity activity) {
          switch (curTheme) {
              case dark:
                  //activity.setTheme(R.style.AppThemeDark);
                  return;
              case light:
                  activity.setTheme(R.style.AppThemeLight);
                  return;
          }
      }
  
      @Override
      public void onCreate() {
          super.onCreate();
  
          // Needs to be setup before anything else tries to access it.
          // Perhaps the constructor is a better place, but then again,
          // it is more deterministic as to when this gets called...
          Preferences.setup(this);
  
          // Clear cached apk files. We used to just remove them after they'd
          // been installed, but this causes problems for proprietary gapps
          // users since the introduction of verification (on pre-4.2 Android),
          // because the install intent says it's finished when it hasn't.
          SharedPreferences prefs = PreferenceManager
                  .getDefaultSharedPreferences(getBaseContext());
          curTheme = Theme.valueOf(prefs.getString(Preferences.PREF_THEME, "dark"));
          if (!prefs.getBoolean(Preferences.PREF_CACHE_APK, false)) {
  
              File local_path = Utils.getApkCacheDir(this);
              // Things can be null if the SD card is not ready - we'll just
              // ignore that and do it next time.
              if (local_path != null) {
                  File[] files = local_path.listFiles();
                  if (files != null) {
                      for (File f : files) {
                          if (f.getName().endsWith(".apk")) {
                              f.delete();
                          }
                      }
                  }
              }
          }
  
          apps = null;
          invalidApps = new ArrayList<String>();
          ctx = getApplicationContext();
          DB.initDB(ctx);
          UpdateService.schedule(ctx);
  
          ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(ctx)
              .discCache(new LimitedAgeDiscCache(
                          new File(StorageUtils.getCacheDirectory(ctx, true),
                              "icons"),
                          new FileNameGenerator() {
                              @Override
                              public String generate(String imageUri) {
                                  return imageUri.substring(
                                      imageUri.lastIndexOf('/') + 1);
                              } },
                          // 30 days in secs: 30*24*60*60 = 2592000
                          2592000)
                      )
              .threadPoolSize(Runtime.getRuntime().availableProcessors() * 2)
              .build();
          ImageLoader.getInstance().init(config);
  
          try {
              SSLContext sc = SSLContext.getInstance("TLS");
              X509TrustManager defaultTrustManager = null;
              
              /*
               * init a trust manager factory with a null keystore to access the system trust managers
               */
              TrustManagerFactory tmf = 
                      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
              KeyStore ks = null;
              tmf.init(ks);
              TrustManager[] mgrs = tmf.getTrustManagers();
              
              if(mgrs.length > 0 && mgrs[0] instanceof X509TrustManager)
                  defaultTrustManager = (X509TrustManager) mgrs[0];
  
              /*
               * compose a chain of trust managers as follows:
               * MemorizingTrustManager -> Pinning Trust Manager -> System Trust Manager
               */
              PinningTrustManager pinMgr = new PinningTrustManager(SystemKeyStore.getInstance(ctx),FDroidCertPins.getPinList(), 0);
              MemorizingTrustManager memMgr = new MemorizingTrustManager(ctx, pinMgr, defaultTrustManager);
              
              /*
               * initialize a SSLContext with the outermost trust manager, use this
               * context to set the default SSL socket factory for the HTTPSURLConnection
               * class.
               */
              sc.init(null, new TrustManager[] {memMgr}, new java.security.SecureRandom());
              HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
          } catch (KeyManagementException e) {
              Log.e("FDroid", "Unable to set up trust manager chain. KeyManagementException");
          } catch (NoSuchAlgorithmException e) {
              Log.e("FDroid", "Unable to set up trust manager chain. NoSuchAlgorithmException");
          } catch (KeyStoreException e) {
              Log.e("FDroid", "Unable to set up trust manager chain. KeyStoreException");
          }
      }
  
      private Context ctx;
  
      // Global list of all known applications.
      private List<DB.App> apps;
  
      // Set when something has changed (database or installed apps) so we know
      // we should invalidate the apps.
      private volatile boolean appsAllInvalid = false;
      private Semaphore appsInvalidLock = new Semaphore(1, false);
      private List<String> invalidApps;
  
      // Set apps invalid. Call this when the database has been updated with
      // new app information, or when the installed packages have changed.
      public void invalidateAllApps() {
          try {
              appsInvalidLock.acquire();
              appsAllInvalid = true;
          } catch (InterruptedException e) {
              // Don't care
          } finally {
              appsInvalidLock.release();
          }
      }
  
      // Invalidate a single app
      public void invalidateApp(String id) {
          Log.d("FDroid", "Invalidating "+id);
          invalidApps.add(id);
      }
  
      // Get a list of all known applications. Should not be called when the
      // database is locked (i.e. between DB.getDB() and db.releaseDB(). The
      // contents should never be modified, it's for reading only.
      public List<DB.App> getApps() {
  
          boolean invalid = false;
          try {
              appsInvalidLock.acquire();
              invalid = appsAllInvalid;
              if (invalid) {
                  appsAllInvalid = false;
                  Log.d("FDroid", "Dropping cached app data");
              }
          } catch (InterruptedException e) {
              // Don't care
          } finally {
              appsInvalidLock.release();
          }
  
          if (apps == null || invalid) {
              try {
                  DB db = DB.getDB();
                  apps = db.getApps(true);
  
              } finally {
                  DB.releaseDB();
              }
          } else if (!invalidApps.isEmpty()) {
              try {
                  DB db = DB.getDB();
                  apps = db.refreshApps(apps, invalidApps);
  
                  invalidApps.clear();
              } finally {
                  DB.releaseDB();
              }
          }
  