/** 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.crashlytics.android.Crashlytics; 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; import io.fabric.sdk.android.Fabric; public class App extends Application { public static final String P4_FORETRUKKEN_GÆT_FRA_STEDPLACERING = "P4_FORETRUKKEN_GÆT_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 NØGLE_advaretOmInstalleretPåSDKort = "erInstalleretPåSDKort"; public static final boolean PRODUKTION = !BuildConfig.DEBUG; public static final boolean PRODUKTION_PÅ_PRØVE = false; // TODO sæt til false public static final boolean ÆGTE_DR = true; private static final String DRAMA_OG_BOG__A_Å_INDLÆST = "DRAMA_OG_BOG__A_Å_INDLÆST"; /** Bruges på nye funktioner - for at tjekke om de altid er opfyldt i felten. Fjernes ved næste udgivelser */ public static final boolean TJEK_ANTAGELSER = !PRODUKTION; public static boolean EMULATOR = true; // Sæt i onCreate(), ellers virker det ikke i std Java public static boolean IKKE_Android_VM = false; // Hvis test fra almindelig JVM 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 fejlsøgning = false; public static Handler forgrundstråd; public static Typeface skrift_gibson; public static Typeface skrift_gibson_fed; public static Typeface skrift_georgia; public static Netvaerksstatus netværk; public static Fjernbetjening fjernbetjening; public static RequestQueue volleyRequestQueue; public static boolean erInstalleretPåSDKort; private DrDiskBasedCache volleyCache; public static EgenTypefaceSpan skrift_gibson_fed_span; public static DRFarver color; public static Resources res; /** Tidsstempel der kan bruges til at afgøre 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; netværk = new Netvaerksstatus(); EMULATOR = Build.PRODUCT.contains("sdk") || Build.MODEL.contains("Emulator") || IKKE_Android_VM; if (!EMULATOR) { // Mint.initAndStartSession(this, getString(PRODUKTION ? R.string.bugsense_nøgle : R.string.bugsense_testnøgle)); // Mint.enableLogging(true); // Mint.setLogging(5); Fabric.with(this, new Crashlytics()); Log.d("Crashlytics startet"); } super.onCreate(); forgrundstråd = 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); fejlsøgning = prefs.getBoolean("fejlsøgning", 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 (ÆGTE_DR) if ("dk.dr.radio".equals(packageName)) { if (!PRODUKTION) App.langToast("Sæt 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.erInstalleretPåSDKort = 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.erInstalleretPåSDKort) prefs.edit().remove(NØGLE_advaretOmInstalleretPåSDKort).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); if (!ÆGTE_DR) FilCache.init(new File(getCacheDir(), "FilCache")); // 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 Netværkslag, da DRs Varnish-servere ofte svarer med HTTP-kode 500, // som skal håndteres som et timeout og at der skal prøves 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 unødvendigt 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 // når P4-valgskærmbilledet at blive instantieret med ukendt placering og foreslår derfor København // Slået fra, da stedplaceringen ikke virker // if (prefs.getString(P4_FORETRUKKEN_GÆT_FRA_STEDPLACERING, null) == null) startP4stedplacering(); try { DRData.instans = new DRData(); DRData.instans.grunddata = new Grunddata(); // Indlæsning af grunddata/stamdata. // Først 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 fælles 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 (!ÆGTE_DR) App.prefs.edit().putBoolean("vispager_title_strip", true).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.læsStreng(res.openRawResource(App.PRODUKTION ? R.raw.grunddata : R.raw.grunddata_udvikling)); DRData.instans.grunddata.parseFællesGrunddata(grunddata); if (App.fejlsøgning && 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 foretrækker P4 er vi nødt til at finde underkanalen kanalkode = tjekP4OgVælgUnderkanal(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 være 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 uændret) throws Exception { if (uændret) return; // ingen grund til at parse det igen kanal.setStreams(json); 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(netværk, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); netværk.onReceive(this, null); // Få opdateret netværksstatus fjernbetjening = new Fjernbetjening(); // udeståendeInitialisering kaldes når aktivitet bliver synlig første gang // - muligvis aldrig hvis app'en kun betjenes via levende ikon } catch (Exception ex) { // Burde der være 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 følgende 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) { if (ÆGTE_DR) Log.e("DRs skrifttyper er ikke tilgængelige", e); skrift_gibson = Typeface.DEFAULT; skrift_gibson_fed = Typeface.DEFAULT_BOLD; skrift_georgia = Typeface.SERIF; } skrift_gibson_fed_span = new EgenTypefaceSpan("Gibson fed", App.skrift_gibson_fed); if (!EMULATOR) AppOpdatering.tjekForNyAPK(this); Log.d("onCreate tog " + (System.currentTimeMillis() - TIDSSTEMPEL_VED_OPSTART) + " ms"); } public static String tjekP4OgVælgUnderkanal(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_GÆT_FRA_STEDPLACERING, "KH4"); Log.d("P4 underkanal=" + kanalkode); } return kanalkode; } public static void advarEvtOmAlarmerHvisInstalleretPåSDkort(Activity akt) { if (App.erInstalleretPåSDKort && prefs.getBoolean(NØGLE_advaretOmInstalleretPåSDKort, false)) { AlertDialog.Builder dialog = new AlertDialog.Builder(akt); dialog.setTitle("SD-kort"); dialog.setIcon(R.drawable.dri_advarsel_hvid); dialog.setMessage("Vækning fungerer muligvis ikke altid, når 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(NØGLE_advaretOmInstalleretPåSDKort, true).commit(); } }); dialog.show(); } } private void startP4stedplacering() { new AsyncTask() { @Override protected Object doInBackground(Object[] params) { try { String p4kanal = P4Stedplacering.findP4KanalnavnFraIP(); if (App.fejlsøgning) App.langToast("p4kanal: " + p4kanal); if (p4kanal != null) prefs.edit().putString(P4_FORETRUKKEN_GÆT_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 når app'en er synlig og telefonen er online */ private Runnable onlineinitialisering = new Runnable() { int forsinkelse = 15000; @Override public void run() { if (!erOnline()) return; boolean færdig = true; Log.d("Onlineinitialisering starter efter " + (System.currentTimeMillis() - TIDSSTEMPEL_VED_OPSTART) + " ms"); if (App.netværk.status == Netvaerksstatus.Status.WIFI) { // Tjek at alle kanaler har deres streamsurler for (final Kanal kanal : DRData.instans.grunddata.kanaler) { if (kanal.harStreams() || Kanal.P4kode.equals(kanal.kode)) 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 uændret) throws Exception { if (uændret) return; kanal.setStreams(json); Log.d("hentStreams app fraCache=" + fraCache + " => " + kanal); } }) { public Priority getPriority() { return Priority.LOW; } }; App.volleyRequestQueue.add(req); } } if (DRData.instans.favoritter.getAntalNyeUdsendelser() < 0) { færdig = false; DRData.instans.favoritter.startOpdaterAntalNyeUdsendelser.run(); } if (!færdig) { Log.d("Onlineinitialisering ikke færdig - prøver igen om " + forsinkelse/1000 +" sekunder"); App.forgrundstråd.removeCallbacks(this); App.forgrundstråd.postDelayed(this, forsinkelse); // prøv igen om 15 sekunder og se om alle data er klar der forsinkelse = 15*forsinkelse/10; } if (prefs.getString(P4_FORETRUKKEN_GÆT_FRA_STEDPLACERING, null) == null) { if (DRData.instans.grunddata.android_json.optBoolean("P4stedplacering", false)) { færdig = false; startP4stedplacering(); } else { prefs.edit().putString(P4_FORETRUKKEN_GÆT_FRA_STEDPLACERING, "defekt").commit(); } } // Forsøg at indlæse Drama&Bog og alle kanaler A-Å én gang ved opstart // Der er givetvis en del der sjældent 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 gøres kun én gang, hvilket skulle dække de fleste scenarier // TODO den rigtige løsning burde være at svarene for Drama&Bog og A-Å bliver hængende i cachen, tjekket her burde være om de er i cachen eller ej if (færdig && !prefs.getBoolean(DRAMA_OG_BOG__A_Å_INDLÆST, false)) { prefs.edit().putBoolean(DRAMA_OG_BOG__A_Å_INDLÆST, true); færdig = false; DRData.instans.dramaOgBog.startHentData(); DRData.instans.programserierAtilÅ.startHentData(); } if (færdig) { netværk.observatører.remove(this); // Hold ikke mere øje med om vi kommer online onlineinitialisering = null; Log.d("Onlineinitialisering færdig"); } } }; 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 uændret) throws Exception { if (uændret || 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.fejlsøgning) App.kortToast("Vi fik nye grunddata"); DRData.instans.grunddata.parseFællesGrunddata(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.observatører) r.run(); // Er vi nået hertil så gik parsning godt - gem de nye stamdata i prefs, så de også bruges ved næste 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 netværskommunikation er påbegyndt eller afsluttet. * Forårsager at det 'drejende hjul' (ProgressBar) vises på den aktivitet der er synlig p.t. * @param netværkErIGang true for påbegyndt og false for afsluttet. */ public static synchronized void sætErIGang(boolean netværkErIGang, String hvad) { boolean før = erIGang > 0; if (App.EMULATOR) { Integer antal = hvadErIGang.get(hvad); antal = (antal==null?0:antal) + (netværkErIGang?1:-1); hvadErIGang.put(hvad, antal); if (antal>1) Log.d("sætErIGang: "+hvad+" har "+antal+" samtidige anmodninger"); else if (antal<0) Log.e(new IllegalStateException("erIGang manglede " + hvad)); else if (netværkErIGang) Log.d("sætErIGang: "+hvad); //if (!netværkErIGang && hvad.trim().length()==0) Log.e(new IllegalStateException("hvad er tom")); } erIGang += netværkErIGang ? 1 : -1; boolean nu = erIGang > 0; if (fejlsøgning) Log.d("erIGang = " + erIGang); if (erIGang < 0) { if (App.EMULATOR) Log.e(new IllegalStateException("erIGang er " + erIGang + " hvadErIGang="+hvadErIGang)); erIGang = 0; } if (før != nu && aktivitetIForgrunden != null) forgrundstråd.post(sætProgressbar); // Fejltjek } private static Runnable sætProgressbar = new Runnable() { public void run() { if (aktivitetIForgrunden instanceof Basisaktivitet) { ((Basisaktivitet) aktivitetIForgrunden).sætProgressBar(erIGang > 0); } } }; public void aktivitetStartet(Activity akt) { senesteAktivitetIForgrunden = aktivitetIForgrunden = akt; sætProgressbar.run(); if (onlineinitialisering != null) { if (App.erOnline()) { App.forgrundstråd.postDelayed(onlineinitialisering, 250); // Initialisér onlinedata } else { App.netværk.observatører.add(onlineinitialisering); // Vent på at vi kommer online og lav så et tjek } } if (kørFørsteGangAppIkkeMereErSynlig != null) forgrundstråd.removeCallbacks(kørFørsteGangAppIkkeMereErSynlig); forgrundstråd.postDelayed(synlighedsSporing, 50); } public void aktivitetStoppet(Activity akt) { if (akt != aktivitetIForgrunden) return; // en anden aktivitet er allerede startet aktivitetIForgrunden = null; if (kørFørsteGangAppIkkeMereErSynlig != null) forgrundstråd.postDelayed(kørFørsteGangAppIkkeMereErSynlig, 1000); forgrundstråd.postDelayed(synlighedsSporing, 50); } Runnable synlighedsSporing = new Runnable() { boolean sidstSynlig = false; @Override public void run() { boolean synligNu = aktivitetIForgrunden!=null; if (sidstSynlig == synligNu) return; sidstSynlig = synligNu; Sidevisning.i().synlig(synligNu); } }; /** * Køres et sekund efter at app'en ikke mere er synlig. * Her rydder vi op i filer */ private Runnable kørFørsteGangAppIkkeMereErSynlig = new Runnable() { @Override public void run() { if (aktivitetIForgrunden != null) return; if (App.fejlsøgning) App.kortToast("kørFørsteGangAppIkkeMereErSynlig"); final int DAGE = 24 * 60 * 60 * 1000; int volleySlettet = volleyCache.sletFilerÆldreEnd(TIDSSTEMPEL_VED_OPSTART-10*DAGE); int aqSlettet = Diverse.sletFilerÆldreEnd(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 unødvendigt overhead på ~ 1MB File gammelVolleyCacheDir = new File(getCacheDir(), "volley"); Diverse.sletFilerÆldreEnd(gammelVolleyCacheDir, TIDSSTEMPEL_VED_OPSTART-7*DAGE); if (fejlsøgning) { App.kortToast("volleyCache: " + volleySlettet / 1000 + " kb frigivet"); App.kortToast("AQ: " + aqSlettet / 1000 + " kb kunne frigivet"); } kørFørsteGangAppIkkeMereErSynlig = 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; forgrundstråd.post(new Runnable() { @Override public void run() { // lange toasts bør blive hængende if (forrigeToast!=null && forrigeToast.getDuration()==Toast.LENGTH_SHORT && !App.fejlsøgning && !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; forgrundstråd.post(new Runnable() { @Override public void run() { // lange toasts bør blive hængende if (forrigeToast!=null && forrigeToast.getDuration()==Toast.LENGTH_SHORT && !App.fejlsøgning && !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 vedhæftning) { 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("text/plain"); i.putExtra(Intent.EXTRA_EMAIL, modtagere); i.putExtra(Intent.EXTRA_SUBJECT, emne); android.util.Log.d("KONTAKT", txt); if (vedhæftning != null) try { String logfil = "programlog.txt"; @SuppressLint("WorldReadableFiles") FileOutputStream fos = akt.openFileOutput(logfil, akt.MODE_WORLD_READABLE); fos.write(vedhæftning.getBytes()); fos.close(); Uri uri = Uri.fromFile(new File(akt.getFilesDir().getAbsolutePath(), logfil)); // https://medium.com/google-developers/sharing-content-between-android-apps-2e6db9d1368b#.kkoqnbkar // Uri uriToImage = FileProvider.getUriForFile( // akt, FILES_AUTHORITY, imageFile); // ?? txt += "\n\nRul op øverst i meddelelsen og giv din feedback, tak."; i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 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 når 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 går 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 sætServerCurrentTimeMillis(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 afgøre om vi er igang med at teste noget fra en main()-metode eller app'en rent faktisk kører */ 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 grå40 = res.getColor(R.color.grå40); public int blå = res.getColor(R.color.blå); public int grå60 = res.getColor(R.color.grå60); } }