dk.dr.radio.diverse.App.java Source code

Java tutorial

Introduction

Here is the source code for dk.dr.radio.diverse.App.java

Source

    /**
     DR Radio 2 is developed by Jacob Nordfalk, Hanafi Mughrabi and Frederik Aagaard.
     Some parts of the code are loosely based on Sveriges Radio Play for Android.
    
     DR Radio 2 for Android is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License version 2 as published by
     the Free Software Foundation.
    
     DR Radio 2 for Android 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
     DR Radio 2 for Android.  If not, see <http://www.gnu.org/licenses/>.
    
     */

    package dk.dr.radio.diverse;

    /**
     *
     * @author j
     */

    import android.annotation.SuppressLint;
    import android.annotation.TargetApi;
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.Application;
    import android.app.NotificationManager;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.content.SharedPreferences;
    import android.content.pm.ApplicationInfo;
    import android.content.pm.PackageInfo;
    import android.content.res.Resources;
    import android.graphics.Typeface;
    import android.media.AudioManager;
    import android.net.ConnectivityManager;
    import android.net.NetworkInfo;
    import android.net.Uri;
    import android.net.http.AndroidHttpClient;
    import android.os.AsyncTask;
    import android.os.Build;
    import android.os.Handler;
    import android.preference.PreferenceManager;
    import android.view.accessibility.AccessibilityManager;
    import android.widget.Toast;

    import com.android.volley.Network;
    import com.android.volley.Request;
    import com.android.volley.RequestQueue;
    import com.android.volley.toolbox.HttpClientStack;
    import com.android.volley.toolbox.HttpStack;
    import com.android.volley.toolbox.HurlStack;
    import com.androidquery.callback.BitmapAjaxCallback;
    import com.bugsense.trace.BugSenseHandler;

    import org.json.JSONObject;

    import java.io.File;
    import java.io.FileOutputStream;
    import java.util.Date;
    import java.util.LinkedHashMap;

    import dk.dr.radio.afspilning.Afspiller;
    import dk.dr.radio.afspilning.Fjernbetjening;
    import dk.dr.radio.akt.Basisaktivitet;
    import dk.dr.radio.data.DRData;
    import dk.dr.radio.data.Grunddata;
    import dk.dr.radio.data.Kanal;
    import dk.dr.radio.net.Diverse;
    import dk.dr.radio.net.Netvaerksstatus;
    import dk.dr.radio.net.volley.DrBasicNetwork;
    import dk.dr.radio.net.volley.DrDiskBasedCache;
    import dk.dr.radio.net.volley.DrVolleyResonseListener;
    import dk.dr.radio.net.volley.DrVolleyStringRequest;
    import dk.dr.radio.v3.BuildConfig;
    import dk.dr.radio.v3.R;

    public class App extends Application {
  public static final String P4_FORETRUKKEN_GT_FRA_STEDPLACERING="P4_FORETRUKKEN_GT_FRA_STEDPLACERING";
        public static final String P4_FORETRUKKEN_AF_BRUGER = "P4_FORETRUKKEN_AF_BRUGER";
        public static final String FORETRUKKEN_KANAL = "FORETRUKKEN_kanal";
  public static final String NGLE_advaretOmInstalleretPSDKort="erInstalleretPSDKort";
        public static final boolean PRODUKTION = !BuildConfig.DEBUG;
  private static final String DRAMA_OG_BOG__A__INDLST="DRAMA_OG_BOG__A__INDLST";
        public static boolean EMULATOR = true; // St i onCreate(), ellers virker det ikke i std Java
        public static App instans;
        public static SharedPreferences prefs;
        public static ConnectivityManager connectivityManager;
        public static String versionsnavn = "(ukendt)";
        public static NotificationManager notificationManager;
        public static AudioManager audioManager;
  public static boolean fejlsgning=false;
        public static Handler forgrundstrd;
        public static Typeface skrift_gibson;
        public static Typeface skrift_gibson_fed;
        public static Typeface skrift_georgia;

  public static Netvaerksstatus netvrk;
        public static Fjernbetjening fjernbetjening;
        public static RequestQueue volleyRequestQueue;
        public static boolean erInstalleretPSDKort;
        private DrDiskBasedCache volleyCache;
        public static EgenTypefaceSpan skrift_gibson_fed_span;
        public static DRFarver color;
        public static Resources res;
        /** Tidsstempel der kan bruges til at afgre hvilke filer der faktisk er brugt efter denne opstart */
        private static long TIDSSTEMPEL_VED_OPSTART;
        public static AccessibilityManager accessibilityManager;
        private static SharedPreferences grunddata_prefs;

  @SuppressLint("NewApi")
  @Override
  public void onCreate() {
    TIDSSTEMPEL_VED_OPSTART = System.currentTimeMillis();
    instans = this;
    netvrk = new Netvaerksstatus();
    EMULATOR = Build.PRODUCT.contains("sdk") || Build.MODEL.contains("Emulator");
    if (!EMULATOR)
      BugSenseHandler.initAndStartSession(this, getString(PRODUKTION ? R.string.bugsense_ngle : R.string.bugsense_testngle));
    super.onCreate();

    forgrundstrd = new Handler();
    connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    audioManager = (AudioManager) App.instans.getSystemService(Context.AUDIO_SERVICE);
    prefs = PreferenceManager.getDefaultSharedPreferences(this);
    fejlsgning = prefs.getBoolean("fejlsgning", false);
    res = App.instans.getResources();
    App.color = new DRFarver();

    // HTTP-forbindelser havde en fejl pr froyo, men jeg har ogs set problemet p Xperia Play, der er 2.3.4 (!)
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
      System.setProperty("http.keepAlive", "false");
    }
    String packageName = getPackageName();
    try {
      if ("dk.dr.radio".equals(packageName)) {
        if (!PRODUKTION) App.langToast("St PRODUKTIONs-flaget");
      } else {
        if (PRODUKTION) App.langToast("Testudgave - fjern PRODUKTIONs-flaget");
      }
      //noinspection ConstantConditions
      PackageInfo pi = getPackageManager().getPackageInfo(packageName, 0);
      App.versionsnavn = packageName + "/" + pi.versionName;
      if (EMULATOR) App.versionsnavn += " EMU";
      Log.d("App.versionsnavn=" + App.versionsnavn);

      App.erInstalleretPSDKort = 0!=(pi.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE);
      /* check for API level 7 - check files dir
      try {
        String filesDir = context.getFilesDir().getAbsolutePath();
        if (filesDir.startsWith("/data/")) {
          return false;
        } else if (filesDir.contains("/mnt/") || filesDir.contains("/sdcard/")) {
          return true;
        }
      } catch (Throwable e) {
        // ignore
      }
      */
      if (!App.erInstalleretPSDKort) prefs.edit().remove(NGLE_advaretOmInstalleretPSDKort).commit();

      Class.forName("android.os.AsyncTask"); // Fix for http://code.google.com/p/android/issues/detail?id=20915
    } catch (Exception e) {
      Log.rapporterFejl(e);
    }

    accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);


    // Initialisering af Volley

    // Prior to Gingerbread, HttpUrlConnection was unreliable.
    // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
    HttpStack stack =
        Build.VERSION.SDK_INT >= 9 ? new HurlStack()
            : Build.VERSION.SDK_INT >= 8 ? new HttpClientStack(AndroidHttpClient.newInstance(App.versionsnavn))
//            : new HttpClientStack(new DefaultHttpClient()); // Android 2.1 -
            : new HurlStack(); // Android 2.1
    // HTTP connection reuse was buggy pre-froyo
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
      System.setProperty("http.keepAlive", "false");
    }
    // Vi bruger vores eget Netvrkslag, da DRs Varnish-servere ofte svarer med HTTP-kode 500,
    // som skal hndteres som et timeout og at der skal prves igen
    Network network = new DrBasicNetwork(stack);
    // Vi bruger vores egen DrDiskBasedCache, da den indbyggede i Volley
    // har en opstartstid p flere sekunder
    // Mappe ndret fra standardmappen "volley" til "dr_volley" 19. nov 2014.
    // Det skyldtes at et hukommelsesdump viste, at Volley indekserede alle filerne i standardmappen,
    // uden om vores implementation, hvilket gav et undvendigt overhead p ~ 1MB
    File cacheDir = new File(getCacheDir(), "dr_volley");
    volleyCache = new DrDiskBasedCache(cacheDir);
    volleyRequestQueue = new RequestQueue(volleyCache, network);
    volleyRequestQueue.start();

    // P4 stedplacering skal ske s tidligt som muligt - ellers
    // nr P4-valgskrmbilledet at blive instantieret med ukendt placering og foreslr derfor Kbenhavn
    if (prefs.getString(P4_FORETRUKKEN_GT_FRA_STEDPLACERING, null) == null) startP4stedplacering();

    try {
      DRData.instans = new DRData();
      DRData.instans.grunddata = new Grunddata();

      // Indlsning af grunddata/stamdata.
      // Frst tjekkes om vi har en udgave i prefs, og ellers bruges den i raw-mappen
      // P et senere tidspunkt henter vi nye grunddata
      grunddata_prefs = App.instans.getSharedPreferences("grunddata", 0);
      String grunddata = grunddata_prefs.getString(DRData.GRUNDDATA_URL, null);

      if (grunddata == null) {
        grunddata = App.prefs.getString(DRData.GRUNDDATA_URL, null);
        if (grunddata!=null) { // 28 nov 2014 - flyt data fra flles prefs til separat fil - kan fjernes ultimo 2015
          App.prefs.edit().remove(DRData.GRUNDDATA_URL).commit();
          grunddata_prefs.edit().putString(DRData.GRUNDDATA_URL, grunddata).commit();
        }
      }

      if (App.prefs.contains("stamdata23") || App.prefs.contains("stamdata24")) {
        // 24 feb 2015 - fjern gamle stamdata fra prefs - kan fjernes primo 2016
        App.prefs.edit().remove("stamdata22").remove("stamdata23").remove("stamdata24").commit();
      }

      if (grunddata == null)
        grunddata = Diverse.lsStreng(res.openRawResource(App.PRODUKTION ? R.raw.grunddata : R.raw.grunddata_udvikling));
      DRData.instans.grunddata.parseFllesGrunddata(grunddata);
      if (App.fejlsgning && DRData.instans.grunddata.udelukHLS) App.kortToast("HLS er udelukket");

      String pn = App.instans.getPackageName();
      for (final Kanal k : DRData.instans.grunddata.kanaler) {
        k.kanallogo_resid = res.getIdentifier("kanalappendix_" + k.kode.toLowerCase().replace('', 'o').replace('', 'a'), "drawable", pn);
      }

      String kanalkode = prefs.getString(FORETRUKKEN_KANAL, null);
      // Hvis brugeren foretrkker P4 er vi ndt til at finde underkanalen
      kanalkode = tjekP4OgVlgUnderkanal(kanalkode);

      Kanal aktuelKanal = DRData.instans.grunddata.kanalFraKode.get(kanalkode);
      if (aktuelKanal == null || aktuelKanal == Grunddata.ukendtKanal) {
        aktuelKanal = DRData.instans.grunddata.forvalgtKanal;
        Log.d("forvalgtKanal=" + aktuelKanal);
      }

      if (!aktuelKanal.harStreams()) { // ikke && App.erOnline(), det kan vre vi har en cachet udgave
        final Kanal kanal = aktuelKanal;
        Request<?> req = new DrVolleyStringRequest(aktuelKanal.getStreamsUrl(), new DrVolleyResonseListener() {
          @Override
          public void fikSvar(String json, boolean fraCache, boolean undret) throws Exception {
            if (undret) return; // ingen grund til at parse det igen
            JSONObject o = new JSONObject(json);
            kanal.setStreams(o);
            Log.d("hentStreams akt fraCache=" + fraCache + " => " + kanal);
          }
        }) {

        public Priority getPriority() {
            return Priority.HIGH;
        }};App.volleyRequestQueue.add(req);}

        DRData.instans.afspiller=new Afspiller();DRData.instans.afspiller.setLydkilde(aktuelKanal);

        registerReceiver(netvrk,new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));netvrk.onReceive(this,null); // F opdateret netvrksstatus
        fjernbetjening=new Fjernbetjening();

        // udestendeInitialisering kaldes nr aktivitet bliver synlig frste gang
        // - muligvis aldrig hvis app'en kun betjenes via levende ikon

        }catch(Exception ex)
        {
      // Burde der vre popop-advarsel til bruger om intern fejl og rapporter til udvikler-dialog ?
      Log.rapporterFejl(ex);
    }

        try
        { // DRs skrifttyper er ikke offentliggjort i SVN, derfor kan flgende fejle:
            skrift_gibson = Typeface.createFromAsset(getAssets(), "Gibson-Regular.otf");
            skrift_gibson_fed = Typeface.createFromAsset(getAssets(), "Gibson-SemiBold.otf");
            skrift_georgia = Typeface.createFromAsset(getAssets(), "Georgia.ttf");
        }catch(
        Exception e)
        {
            Log.e("DRs skrifttyper er ikke tilgngelige", e);
            skrift_gibson = Typeface.DEFAULT;
            skrift_gibson_fed = Typeface.DEFAULT_BOLD;
            skrift_georgia = Typeface.DEFAULT;
        }skrift_gibson_fed_span=new EgenTypefaceSpan("Gibson fed",App.skrift_gibson_fed);

        Log.d("onCreate tog "+(System.currentTimeMillis()-TIDSSTEMPEL_VED_OPSTART)+" ms");}

        public static String tjekP4OgVlgUnderkanal(String kanalkode) {
    if (Kanal.P4kode.equals(kanalkode)) {
      kanalkode = App.prefs.getString(App.P4_FORETRUKKEN_AF_BRUGER, null);
      if (kanalkode == null) kanalkode = App.prefs.getString(App.P4_FORETRUKKEN_GT_FRA_STEDPLACERING, "KH4");
      Log.d("P4 underkanal=" + kanalkode);
    }
    return kanalkode;
  }

        public static void advarEvtOmAlarmerHvisInstalleretPSDkort(Activity akt) {
            if(App.erInstalleretPSDKort&&prefs.getBoolean(NGLE_advaretOmInstalleretPSDKort,false)){AlertDialog.Builder dialog=new AlertDialog.Builder(akt);dialog.setTitle("SD-kort");dialog.setIcon(R.drawable.dri_advarsel_hvid);dialog.setMessage("Vkning fungerer muligvis ikke altid, nr DR Radio er flyttet til SD-kort");dialog.setPositiveButton(android.R.string.ok,new AlertDialog.OnClickListener(){public void onClick(DialogInterface arg0,int arg1){prefs.edit().putBoolean(NGLE_advaretOmInstalleretPSDKort,true).commit();}});dialog.show();}
        }

        private void startP4stedplacering() {
            new AsyncTask(){@Override protected Object doInBackground(Object[]params){try{String p4kanal=P4Stedplacering.findP4KanalnavnFraIP();if(App.fejlsgning)App.langToast("p4kanal: "+p4kanal);if(p4kanal!=null)prefs.edit().putString(P4_FORETRUKKEN_GT_FRA_STEDPLACERING,p4kanal).commit();
            //if (!App.PRODUKTION) Log.rapporterFejl(new Exception("Ny enhed - fundet P4-kanal " + p4kanal));
            }catch(Exception e){e.printStackTrace();}return null;}}.execute();
        }

  /**
   * Initialisering af resterende data.
   * Dette sker nr app'en er synlig og telefonen er online
   */
  private Runnable onlineinitialisering = new Runnable() {
    int forsinkelse = 15000;
    @Override
    public void run() {
      if (!erOnline()) return;
      boolean frdig = true;
      Log.d("Onlineinitialisering starter efter " + (System.currentTimeMillis() - TIDSSTEMPEL_VED_OPSTART) + " ms");

      if (App.netvrk.status == Netvaerksstatus.Status.WIFI) { // Tjek at alle kanaler har deres streamsurler
        for (final Kanal kanal : DRData.instans.grunddata.kanaler) {
          if (kanal.harStreams()) continue;
          //        Log.d("run()1 " + (System.currentTimeMillis() - TIDSSTEMPEL_VED_OPSTART) + " ms");
          Request<?> req = new DrVolleyStringRequest(kanal.getStreamsUrl(), new DrVolleyResonseListener() {
            @Override
            public void fikSvar(String json, boolean fraCache, boolean undret) throws Exception {
              if (undret) return;
              JSONObject o = new JSONObject(json);
              kanal.setStreams(o);
              Log.d("hentStreams app fraCache=" + fraCache + " => " + kanal);
            }
          }) {
            public Priority getPriority() {
              return Priority.LOW;
            }
          };
          App.volleyRequestQueue.add(req);}}

        if(DRData.instans.favoritter.getAntalNyeUdsendelser()<0){frdig=false;DRData.instans.favoritter.startOpdaterAntalNyeUdsendelser.run();}

        if(!frdig){Log.d("Onlineinitialisering ikke frdig - prver igen om "+forsinkelse/1000+" sekunder");App.forgrundstrd.removeCallbacks(this);App.forgrundstrd.postDelayed(this,forsinkelse); // prv igen om 15 sekunder og se om alle data er klar der
        forsinkelse=15*forsinkelse/10;}

        if(prefs.getString(P4_FORETRUKKEN_GT_FRA_STEDPLACERING,null)==null){if(DRData.instans.grunddata.android_json.optBoolean("P4stedplacering",false)){frdig=false;startP4stedplacering();}else{prefs.edit().putString(P4_FORETRUKKEN_GT_FRA_STEDPLACERING,"defekt").commit();}}

        // Forsg at indlse Drama&Bog og alle kanaler A- n gang ved opstart
        // Der er givetvis en del der sjldent bruger disse funktioner,
        // og hvis telefonen tror den er online men man ikke kan f forbindelse,
        // kan der komme rigtig mange store anomdninger i k
        //  - det gres kun n gang, hvilket skulle dkke de fleste scenarier
        // TODO den rigtige lsning burde vre at svarene for Drama&Bog og A- bliver hngende i cachen, tjekket her burde vre om de er i cachen eller ej
        if(frdig&&!prefs.getBoolean(DRAMA_OG_BOG__A__INDLST,false)){prefs.edit().putBoolean(DRAMA_OG_BOG__A__INDLST,true);frdig=false;DRData.instans.dramaOgBog.startHentData();DRData.instans.programserierAtil.startHentData();}if(frdig){netvrk.observatrer.remove(this); // Hold ikke mere je med om vi kommer online
        onlineinitialisering=null;Log.d("Onlineinitialisering frdig");}}};

  public static Runnable hentEvtNyeGrunddata = new Runnable() {
    long sidstTjekket = 0;

    @Override
    public void run() {
      if (!App.erOnline()) return;
      if (sidstTjekket + (App.EMULATOR ? 1000 : DRData.instans.grunddata.opdaterGrunddataEfterMs) > System.currentTimeMillis())
        return;
      sidstTjekket = System.currentTimeMillis();
      Log.d("hentEvtNyeGrunddata " + (sidstTjekket - App.TIDSSTEMPEL_VED_OPSTART));
      Request<?> req = new DrVolleyStringRequest(DRData.GRUNDDATA_URL, new DrVolleyResonseListener() {
        @Override
        public void fikSvar(String nyeGrunddata, boolean fraCache, boolean undret) throws Exception {
          if (undret || fraCache) return; // ingen grund til at parse det igen
          String gamleGrunddata = grunddata_prefs.getString(DRData.GRUNDDATA_URL, null);
          if (nyeGrunddata.equals(gamleGrunddata)) return; // Det samme som var i prefs
          Log.d("Vi fik nye grunddata: fraCache=" + fraCache + nyeGrunddata);
          if (!PRODUKTION || App.fejlsgning) App.kortToast("Vi fik nye grunddata");
          DRData.instans.grunddata.parseFllesGrunddata(nyeGrunddata);
          String pn = App.instans.getPackageName();
          for (final Kanal k : DRData.instans.grunddata.kanaler) {
            k.kanallogo_resid = res.getIdentifier("kanalappendix_" + k.kode.toLowerCase().replace('', 'o').replace('', 'a'), "drawable", pn);
          }
          // fix for https://mint.splunk.com/dashboard/project/cd78aa05/errors/2774928662
          for (Runnable r : DRData.instans.grunddata.observatrer) r.run();
          // Er vi net hertil s gik parsning godt - gem de nye stamdata i prefs, s de ogs bruges ved nste opstart
          grunddata_prefs.edit().putString(DRData.GRUNDDATA_URL, nyeGrunddata).commit();
        }
      }) {
        public Priority getPriority() {
          return Priority.LOW;
        }
      };
      App.volleyRequestQueue.add(req);
    }};

        /*
         * Kilde: http://developer.android.com/training/basics/network-ops/managing.html
         */
        public static boolean erOnline() {
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            return (networkInfo != null && networkInfo.isConnected());
        }

        public static Activity aktivitetIForgrunden = null;
        public static Activity senesteAktivitetIForgrunden = null;
        private static int erIGang = 0;

        private static LinkedHashMap<String, Integer> hvadErIGang = new LinkedHashMap<String, Integer>();
  /**
   * Signalerer over for brugeren at netvrskommunikation er pbegyndt eller afsluttet.
   * Forrsager at det 'drejende hjul' (ProgressBar) vises p den aktivitet der er synlig p.t.
   * @param netvrkErIGang true for pbegyndt og false for afsluttet.
   */
  public static synchronized void stErIGang(boolean netvrkErIGang, String hvad) {
    boolean fr = erIGang > 0;
    if (App.EMULATOR) {
      Integer antal = hvadErIGang.get(hvad);
      antal = (antal==null?0:antal) + (netvrkErIGang?1:-1);
      hvadErIGang.put(hvad, antal);
      if (antal>1) Log.d("stErIGang: "+hvad+" har "+antal+" samtidige anmodninger");
      else if (antal<0) Log.e(new IllegalStateException("erIGang manglede " + hvad));
      else if (netvrkErIGang) Log.d("stErIGang: "+hvad);
    }
    erIGang += netvrkErIGang ? 1 : -1;
    boolean nu = erIGang > 0;
    if (fejlsgning) Log.d("erIGang = " + erIGang);
    if (erIGang < 0) {
      if (App.EMULATOR) Log.e(new IllegalStateException("erIGang er " + erIGang + " hvadErIGang="+hvadErIGang));
      erIGang = 0;
    }
    if (fr != nu && aktivitetIForgrunden != null) forgrundstrd.post(stProgressbar);
    // Fejltjek
  }

  private static Runnable stProgressbar = new Runnable() {
    public void run() {
      if (aktivitetIForgrunden instanceof Basisaktivitet) {
        ((Basisaktivitet) aktivitetIForgrunden).stProgressBar(erIGang > 0);
      }
    }
  };

        public void aktivitetStartet(Activity akt) {
            senesteAktivitetIForgrunden=aktivitetIForgrunden=akt;stProgressbar.run();if(onlineinitialisering!=null){if(App.erOnline()){App.forgrundstrd.postDelayed(onlineinitialisering,250); // Initialisr onlinedata
            }else{App.netvrk.observatrer.add(onlineinitialisering); // Vent p at vi kommer online og lav s et tjek
            }}if(krFrsteGangAppIkkeMereErSynlig!=null)forgrundstrd.removeCallbacks(krFrsteGangAppIkkeMereErSynlig);
        }

  public void aktivitetStoppet(Activity akt) {
    if (akt != aktivitetIForgrunden) return; // en anden aktivitet er allerede startet
    aktivitetIForgrunden = null;
    if (krFrsteGangAppIkkeMereErSynlig != null) forgrundstrd.postDelayed(krFrsteGangAppIkkeMereErSynlig, 1000);
  }

  /**
   * Kres et sekund efter at app'en ikke mere er synlig.
   * Her rydder vi op i filer
   */
  private Runnable krFrsteGangAppIkkeMereErSynlig = new Runnable() {
    @Override
    public void run() {
      if (aktivitetIForgrunden != null) return;
      if (App.fejlsgning) App.kortToast("krFrsteGangAppIkkeMereErSynlig");
      final int DAGE = 24 * 60 * 60 * 1000;
      int volleySlettet = volleyCache.sletFilerldreEnd(TIDSSTEMPEL_VED_OPSTART-10*DAGE);
      int aqSlettet = Diverse.sletFilerldreEnd(new File(getCacheDir(), "aquery"), TIDSSTEMPEL_VED_OPSTART-4*DAGE);
      // Mappe ndret fra standardmappen "volley" til "dr_volley" 19. nov 2014.
      // Det skyldtes at et hukommelsesdump viste, at Volley indekserede alle filerne i standardmappen,
      // uden om vores implementation, hvilket gav et undvendigt overhead p ~ 1MB
      File gammelVolleyCacheDir = new File(getCacheDir(), "volley");
      Diverse.sletFilerldreEnd(gammelVolleyCacheDir, TIDSSTEMPEL_VED_OPSTART-7*DAGE);

      if (fejlsgning) {
        App.kortToast("volleyCache: " + volleySlettet / 1000 + " kb frigivet");
        App.kortToast("AQ: " + aqSlettet / 1000 + " kb kunne frigivet");
      }
      krFrsteGangAppIkkeMereErSynlig = null;
    }
  };

        private static Toast forrigeToast;

        public static void langToast(String txt) {
            Log.d("langToast("+txt);if(aktivitetIForgrunden==null)txt="DR Radio:\n"+txt;final String txt2=txt;forgrundstrd.post(new Runnable(){@Override public void run(){
            // lange toasts br blive hngende
            if(forrigeToast!=null&&forrigeToast.getDuration()==Toast.LENGTH_SHORT&&!App.fejlsgning&&!App.EMULATOR)forrigeToast.cancel();forrigeToast=Toast.makeText(instans,txt2,Toast.LENGTH_LONG);forrigeToast.show();}});
        }

        public static void kortToast(String txt) {
            Log.d("kortToast("+txt);if(aktivitetIForgrunden==null)txt="DR Radio:\n"+txt;final String txt2=txt;forgrundstrd.post(new Runnable(){@Override public void run(){
            // lange toasts br blive hngende
            if(forrigeToast!=null&&forrigeToast.getDuration()==Toast.LENGTH_SHORT&&!App.fejlsgning&&!App.EMULATOR)forrigeToast.cancel();forrigeToast=Toast.makeText(instans,txt2,Toast.LENGTH_SHORT);forrigeToast.show();}});
        }

        public static void kortToast(int resId) {
            kortToast(instans.getResources().getString(resId));
        }

        public static void langToast(int resId) {
            langToast(instans.getResources().getString(resId));
        }

  public static void kontakt(Activity akt, String emne, String txt, String vedhftning) {
    String[] modtagere;
    try {
      modtagere = Diverse.jsonArrayTilArrayListString(DRData.instans.grunddata.android_json.getJSONArray("kontakt_modtagere")).toArray(new String[0]);
    } catch (Exception ex) {
      Log.e(ex);
      modtagere = new String[]{"jacob.nordfalk@gmail.com"};
    }

    Intent i = new Intent(Intent.ACTION_SEND);
    i.setType("plain/text");
    i.putExtra(Intent.EXTRA_EMAIL, modtagere);
    i.putExtra(Intent.EXTRA_SUBJECT, emne);


    android.util.Log.d("KONTAKT", txt);
    if (vedhftning != null) try {
      String logfil = "programlog.txt";
      @SuppressLint("WorldReadableFiles") FileOutputStream fos = akt.openFileOutput(logfil, akt.MODE_WORLD_READABLE);
      fos.write(vedhftning.getBytes());
      fos.close();
      Uri uri = Uri.fromFile(new File(akt.getFilesDir().getAbsolutePath(), logfil));
      txt += "\n\nRul op verst i meddelelsen og giv din feedback, tak.";
      i.putExtra(Intent.EXTRA_STREAM, uri);
    } catch (Exception e) {
      Log.e(e);
      txt += "\n" + e;
    }
    i.putExtra(Intent.EXTRA_TEXT, txt);
//    akt.startActivity(Intent.createChooser(i, "Send meddelelse..."));
    try {
      akt.startActivity(i);
    } catch (Exception e) {
      App.langToast(e.toString());
      Log.rapporterFejl(e);
    }
  }

        @Override
        public void onLowMemory() {
            // Ryd op nr der mangler RAM
            BitmapAjaxCallback.clearCache();
            super.onLowMemory();
        }

        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        @Override
        public void onTrimMemory(int level) {
            if (level >= TRIM_MEMORY_BACKGROUND)
                BitmapAjaxCallback.clearCache();
            super.onTrimMemory(level);
        }

        /**
         * I fald telefonens ur gr forkert kan det ses her - alle HTTP-svar bliver jo stemplet med servertiden
         */
        private static long serverkorrektionTilKlienttidMs = 0;

        /**
         * Giver et aktuelt tidsstempel p hvad serverens ur viser
         * @return tiden, i  millisekunder siden 1. Januar 1970 00:00:00.0 UTC.
         */
        public static long serverCurrentTimeMillis() {
            return System.currentTimeMillis() + serverkorrektionTilKlienttidMs;
        }

  public static void stServerCurrentTimeMillis(long servertid) {
    long serverkorrektionTilKlienttidMs2 = servertid - System.currentTimeMillis();
    if (Math.abs(App.serverkorrektionTilKlienttidMs - serverkorrektionTilKlienttidMs2) > 120 * 1000) {
      Log.d("SERVERTID korrigerer tid med " + ((serverkorrektionTilKlienttidMs2 + App.serverkorrektionTilKlienttidMs) / 1000 / 60) + " minutter fra " + new Date(serverCurrentTimeMillis()) + " til " + new Date(servertid));
      App.serverkorrektionTilKlienttidMs = serverkorrektionTilKlienttidMs2;
      new Exception("SERVERTID korrigeret med " + serverkorrektionTilKlienttidMs2 / 1000 / 60 + " min til " + new Date(servertid)).printStackTrace();
    }
  }

        /** Kan kaldet til at afgre om vi er igang med at teste noget fra en main()-metode eller app'en rent faktisk krer */
        public static boolean testFraMain() {
            return instans == null;
        }

        /**
         * Lille klasse der holder nogle farver vi ikke gider sl op i resurser efter hele tiden
         */
        public static class DRFarver {
            public int gr40 = res.getColor(R.color.gr40);
            public int bl = res.getColor(R.color.bl);
            public int gr60 = res.getColor(R.color.gr60);
        }
    }