/*
* TV-Browser for Android
* Copyright (C) 2013 René Mach (rene@tvbrowser.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to use, copy, modify or merge the Software,
* furthermore to publish and distribute the Software free of charge without modifications and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.tvbrowser.utils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HttpsURLConnection;
import org.tvbrowser.content.TvBrowserContentProvider;
import org.tvbrowser.devplugin.Channel;
import org.tvbrowser.settings.SettingConstants;
import org.tvbrowser.tvbrowser.AutoDataUpdateReceiver;
import org.tvbrowser.tvbrowser.Logging;
import org.tvbrowser.tvbrowser.R;
import org.tvbrowser.tvbrowser.ReminderBroadcastReceiver;
import org.tvbrowser.tvbrowser.ServiceUpdateDataTable;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Environment;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
/**
* Helper class to supper IO.
* <p>
* @author René Mach
*/
public class IOUtils {
private static final int DATA_UPDATE_KEY = 1234;
private static final int REQUEST_CODE_DATA_TABLE_UPDATE = 1235;
private static final float MIN_BATTERIE_LEVEL = 0.1f;
/**
* Creates an integer value from the given byte array.
* <p>
* @param value The byte array to convert (Big-Endian).
* @return The calculated integer value.
*/
public static int getIntForBytes(byte[] value) {
int count = value.length - 1;
int result = 0;
for(byte b : value) {
result = result | ((((int)b) & 0xFF) << (count * 8));
count--;
}
return result;
}
public static int INFO_BLACK_AND_WHITE = 1 << 1;
public static int INFO_BLACK_FOUR_TO_THREE = 1 << 2;
public static int INFO_BLACK_SIXTEEN_TO_NINE = 1 << 3;
public static int INFO_BLACK_MONO = 1 << 4;
public static int INFO_BLACK_STEREO = 1 << 5;
public static int INFO_BLACK_DOLBY_SOURROUND = 1 << 6;
public static int INFO_BLACK_DOLBY_DIGITAL = 1 << 7;
public static int INFO_BLACK_SECOND_AUDIO_PROGRAM = 1 << 8;
public static int INFO_BLACK_SECOND_CLOSED_CAPTION = 1 << 9;
public static int INFO_BLACK_SECOND_LIVE = 1 << 10;
public static int INFO_BLACK_SECOND_OMU = 1 << 11;
public static int INFO_BLACK_SECOND_FILM = 1 << 12;
public static int INFO_BLACK_SECOND_SERIES = 1 << 13;
public static int INFO_BLACK_SECOND_NEW = 1 << 14;
public static int INFO_BLACK_SECOND_AUDIO_DESCRIPTION = 1 << 15;
public static int INFO_BLACK_SECOND_NEWS = 1 << 16;
public static int INFO_BLACK_SECOND_SHOW = 1 << 17;
public static int INFO_BLACK_SECOND_MAGAZIN = 1 << 18;
public static int INFO_BLACK_SECOND_HD = 1 << 19;
public static int INFO_BLACK_SECOND_DOCU = 1 << 20;
public static int INFO_BLACK_SECOND_ART = 1 << 21;
public static int INFO_BLACK_SECOND_SPORT = 1 << 22;
public static int INFO_BLACK_SECOND_CHILDREN = 1 << 23;
public static int INFO_BLACK_SECOND_OTHER = 1 << 24;
public static int INFO_BLACK_SECOND_SIGN_LANGUAGE = 1 << 25;
public static final int[] INFO_CATEGORIES_ARRAY = {
INFO_BLACK_AND_WHITE,
INFO_BLACK_FOUR_TO_THREE,
INFO_BLACK_SIXTEEN_TO_NINE,
INFO_BLACK_MONO,
INFO_BLACK_STEREO,
INFO_BLACK_DOLBY_SOURROUND,
INFO_BLACK_DOLBY_DIGITAL,
INFO_BLACK_SECOND_AUDIO_PROGRAM,
INFO_BLACK_SECOND_CLOSED_CAPTION,
INFO_BLACK_SECOND_LIVE,
INFO_BLACK_SECOND_OMU,
INFO_BLACK_SECOND_FILM,
INFO_BLACK_SECOND_SERIES,
INFO_BLACK_SECOND_NEW,
INFO_BLACK_SECOND_AUDIO_DESCRIPTION,
INFO_BLACK_SECOND_NEWS,
INFO_BLACK_SECOND_SHOW,
INFO_BLACK_SECOND_MAGAZIN,
INFO_BLACK_SECOND_HD,
INFO_BLACK_SECOND_DOCU,
INFO_BLACK_SECOND_ART,
INFO_BLACK_SECOND_SPORT,
INFO_BLACK_SECOND_CHILDREN,
INFO_BLACK_SECOND_OTHER,
INFO_BLACK_SECOND_SIGN_LANGUAGE,
};
public static final String[] getInfoStringArrayNames(Resources res) {
return new String[]{
res.getString(R.string.info_black_and_white),
res.getString(R.string.info_four_to_three),
res.getString(R.string.info_sixteen_to_nine),
res.getString(R.string.info_mono),
res.getString(R.string.info_stereo),
res.getString(R.string.info_dolby_sourround),
res.getString(R.string.info_dolby_digital),
res.getString(R.string.info_second_audio_program),
res.getString(R.string.info_closed_caption),
res.getString(R.string.info_live),
res.getString(R.string.info_omu),
res.getString(R.string.info_film),
res.getString(R.string.info_series),
res.getString(R.string.info_new),
res.getString(R.string.info_audio_description),
res.getString(R.string.info_news),
res.getString(R.string.info_show),
res.getString(R.string.info_magazin),
res.getString(R.string.info_hd),
res.getString(R.string.info_docu),
res.getString(R.string.info_art),
res.getString(R.string.info_sport),
res.getString(R.string.info_children),
res.getString(R.string.info_other),
res.getString(R.string.info_sign_language)
};
}
public static boolean infoSet(int categories, int info) {
return ((categories & info) == info);
}
public static Spannable getInfoString(int value, Resources res) {
return getInfoString(value, res, true);
}
private static int getDefaultCategoryColorKeyForColorKey(int colorKey) {
int defaultColorCategoryKey = R.string.pref_color_categories_default;
if(colorKey == R.string.PREF_COLOR_CATEGORY_FILM) {
defaultColorCategoryKey = R.string.pref_color_category_film_default;
}
else if(colorKey == R.string.PREF_COLOR_CATEGORY_SERIES) {
defaultColorCategoryKey = R.string.pref_color_category_series_default;
}
else if(colorKey == R.string.PREF_COLOR_CATEGORY_NEW) {
defaultColorCategoryKey = R.string.pref_color_category_new_default;
}
else if(colorKey == R.string.PREF_COLOR_CATEGORY_DOCU || colorKey == R.string.PREF_COLOR_CATEGORY_MAGAZIN) {
defaultColorCategoryKey = R.string.pref_color_category_docu_default;
}
else if(colorKey == R.string.PREF_COLOR_CATEGORY_CHILDREN) {
defaultColorCategoryKey = R.string.pref_color_category_children_default;
}
else if(colorKey == R.string.PREF_COLOR_CATEGORY_SHOW) {
defaultColorCategoryKey = R.string.pref_color_category_show_default;
}
return defaultColorCategoryKey;
}
public static HashMap<String,Integer> loadCategoryColorMap(Context context) {
HashMap<String, Integer> categoryColorMap = new HashMap<String, Integer>();
String[] names = getInfoStringArrayNames(context.getResources());
for(int i = 0; i < SettingConstants.CATEGORY_COLOR_PREF_KEY_ARR.length; i++) {
int colorKey = SettingConstants.CATEGORY_COLOR_PREF_KEY_ARR[i];
int[] colorCategory = getActivatedColorFor(PrefUtils.getStringValue(colorKey, getDefaultCategoryColorKeyForColorKey(colorKey)));
if(colorCategory[0] == 1) {
categoryColorMap.put(names[i], colorCategory[1]);
}
}
return categoryColorMap;
}
public static Spannable getInfoString(int value, Resources res, boolean colored) {
return getInfoString(value, res, colored, null);
}
public static Spannable getInfoString(int value, Resources res, boolean colored, Integer defaultColor) {
String[] valueArr = {"",
res.getString(R.string.info_black_and_white),
res.getString(R.string.info_four_to_three),
res.getString(R.string.info_sixteen_to_nine),
res.getString(R.string.info_mono),
res.getString(R.string.info_stereo),
res.getString(R.string.info_dolby_sourround),
res.getString(R.string.info_dolby_digital),
res.getString(R.string.info_second_audio_program),
res.getString(R.string.info_closed_caption),
res.getString(R.string.info_live),
res.getString(R.string.info_omu),
res.getString(R.string.info_film),
res.getString(R.string.info_series),
res.getString(R.string.info_new),
res.getString(R.string.info_audio_description),
res.getString(R.string.info_news),
res.getString(R.string.info_show),
res.getString(R.string.info_magazin),
res.getString(R.string.info_hd),
res.getString(R.string.info_docu),
res.getString(R.string.info_art),
res.getString(R.string.info_sport),
res.getString(R.string.info_children),
res.getString(R.string.info_other),
res.getString(R.string.info_sign_language)
};
int[] prefKeyArr = SettingConstants.CATEGORY_PREF_KEY_ARR;
int[] colorPrefKeyArr = SettingConstants.CATEGORY_COLOR_PREF_KEY_ARR;
SpannableStringBuilder infoString = new SpannableStringBuilder();
for(int i = 1; i <= 25; i++) {
if((value & (1 << i)) == (1 << i) && PrefUtils.getBooleanValue(prefKeyArr[i-1], R.bool.pref_info_show_default)) {
if(infoString.length() > 0) {
infoString.append(", ");
if(defaultColor != null) {
infoString.setSpan(new ForegroundColorSpan(defaultColor), infoString.length()-2, infoString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
infoString.append(valueArr[i]);
if(colored) {
int[] colorCategory = getActivatedColorFor(PrefUtils.getStringValue(colorPrefKeyArr[i-1], getDefaultCategoryColorKeyForColorKey(colorPrefKeyArr[i-1])));
Integer color = defaultColor;
if(colorCategory[0] == 1) {
color = colorCategory[1];
}
if(color != null) {
infoString.setSpan(new ForegroundColorSpan(color), infoString.length()-valueArr[i].length(), infoString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
if(infoString.length() > 0) {
return infoString;
}
return null;
}
/*
public static SpannableString setColor(SpannableString categories, String value, int color, boolean foreground) {
int index = categories.toString().indexOf(value);
if(index != -1) {
categories.setSpan(foreground ? new ForegroundColorSpan(color) : new BackgroundColorSpan(color), index, index+value.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return categories;
}*/
public static byte[] loadUrl(String urlString) throws MalformedURLException, IOException, TimeoutException {
return loadUrl(urlString, 30000);
}
public static byte[] loadUrl(final String urlString, final int timeout) throws MalformedURLException, IOException, TimeoutException {
final AtomicInteger count = new AtomicInteger(0);
final AtomicReference<byte[]> loadData = new AtomicReference<byte[]>(null);
new Thread("LOAD URL THREAD") {
public void run() {
FileOutputStream fout = null;
try {
byte[] byteArr = loadUrl(urlString, count);
loadData.set(byteArr);
}
catch(IOException e) {
}
finally {
close(fout);
}
}
}.start();
Thread wait = new Thread("SAVE URL WAITING THREAD") {
public void run() {
while(loadData.get() == null && count.getAndIncrement() < (timeout / 100)) {
try {
sleep(100);
} catch (InterruptedException e) {}
}
}
};
wait.start();
try {
wait.join();
} catch (InterruptedException e) {}
if(loadData.get() == null) {
throw new TimeoutException("URL '"+urlString+"' could not be saved.");
}
return loadData.get();
}
private static byte[] loadUrl(final String urlString, final AtomicInteger timeoutCount) throws MalformedURLException, IOException {
BufferedInputStream in = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
URLConnection connection = null;
try {
connection = new URL(urlString).openConnection();
IOUtils.setConnectionTimeout(connection,15000);
if(urlString.toLowerCase(Locale.US).endsWith(".gz")) {
connection.setRequestProperty("Accept-Encoding", "gzip,deflate");
}
in = new BufferedInputStream(connection.getInputStream());
byte temp[] = new byte[1024];
int count;
while ((count = in.read(temp, 0, 1024)) != -1) {
if(temp != null && count > 0) {
out.write(temp, 0, count);
if(timeoutCount != null) {
timeoutCount.set(0);
}
}
}
}
finally {
close(in);
disconnect(connection);
}
return out.toByteArray();
}
/**
* Save given URL to filename with a default timeout of 30 seconds.
* <p>
* @param filename The file to save to.
* @param urlString The URL to load from.
* <p>
* @return <code>true</code> if the file was downloaded successfully, <code>false</code> otherwise.
*/
public static boolean saveUrl(final String filename, final String urlString) {
return saveUrl(filename, urlString, 30000);
}
/**
* Save given URL to filename.
* <p>
* @param filename The file to save to.
* @param urlString The URL to load from.
* @param timeout The timeout of the download in milliseconds.
* <p>
* @return <code>true</code> if the file was downloaded successfully, <code>false</code> otherwise.
*/
public static boolean saveUrl(final String filename, final String urlString, final int timeout) {
final AtomicBoolean wasSaved = new AtomicBoolean(false);
final AtomicInteger count = new AtomicInteger(0);
new Thread("SAVE URL THREAD") {
public void run() {
FileOutputStream fout = null;
try {
byte[] byteArr = loadUrl(urlString, count);
fout = new FileOutputStream(filename);
fout.getChannel().truncate(0);
fout.write(byteArr, 0, byteArr.length);
fout.flush();
wasSaved.set(true);
}
catch(IOException e) {
}
finally {
close(fout);
}
}
}.start();
Thread wait = new Thread("SAVE URL WAITING THREAD") {
public void run() {
while(!wasSaved.get() && count.getAndIncrement() < (timeout / 100)) {
try {
sleep(100);
} catch (InterruptedException e) {}
}
}
};
wait.start();
try {
wait.join();
} catch (InterruptedException e) {
Log.d("info51", "INTERRUPTED", e);
}
return wasSaved.get();
}
/**
* Save given URL to filename with a default timeout of 30 seconds.
* <p>
* @param filename The file to save to.
* @param in The input stream to load from.
* <p>
* @return <code>true</code> if the file was downloaded successfully, <code>false</code> otherwise.
*/
public static boolean saveStream(final String filename, final InputStream in) {
return saveStream(filename, in, 30000);
}
/**
* Save given URL to filename.
* <p>
* @param filename The file to save to.
* @param in The input stream to load from.
* @param timeout The timeout of the download in milliseconds.
* <p>
* @return <code>true</code> if the file was downloaded successfully, <code>false</code> otherwise.
*/
public static boolean saveStream(final String filename, final InputStream in, final int timeout) {
final AtomicBoolean wasSaved = new AtomicBoolean(false);
final AtomicInteger count = new AtomicInteger(0);
new Thread("SAVE URL THREAD") {
public void run() {
FileOutputStream fout = null;
try {
fout = new FileOutputStream(filename);
fout.getChannel().truncate(0);
byte[] buffer = new byte[10240];
int len = 0;
while((len = in.read(buffer)) >= 0) {
fout.write(buffer, 0, len);
}
fout.flush();
wasSaved.set(true);
}
catch(IOException e) {
}
finally {
close(fout);
}
}
}.start();
Thread wait = new Thread("SAVE URL WAITING THREAD") {
public void run() {
while(!wasSaved.get() && count.getAndIncrement() < (timeout / 100)) {
try {
sleep(100);
} catch (InterruptedException e) {}
}
}
};
wait.start();
try {
wait.join();
} catch (InterruptedException e) {
Log.d("info51", "INTERRUPTED", e);
}
return wasSaved.get();
}
/*
* Copied from http://stackoverflow.com/questions/4818468/how-to-check-if-inputstream-is-gzipped and changed.
* No license given on page.
*/
public static InputStream decompressStream(InputStream input) throws IOException {
PushbackInputStream pb = new PushbackInputStream( input, 2 ); //we need a pushbackstream to look ahead
byte [] signature = new byte[2];
int read = pb.read( signature ); //read the signature
if(read == 2) {
pb.unread( signature ); //push back the signature to the stream
}
else if(read == 1) {
pb.unread(signature[0]);
}
if(signature[ 0 ] == (byte) (GZIPInputStream.GZIP_MAGIC & 0xFF) && signature[ 1 ] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8) ) {//check if matches standard gzip magic number
return decompressStream(new GZIPInputStream(pb));
}
else {
return pb;
}
}
public static byte[] getCompressedData(byte[] uncompressed) {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
GZIPOutputStream out = null;
try {
out = new GZIPOutputStream(bytesOut);
// SEND THE IMAGE
int index = 0;
int size = 1024;
do {
if ((index + size) > uncompressed.length) {
size = uncompressed.length - index;
}
out.write(uncompressed, index, size);
index += size;
} while (index < uncompressed.length);
out.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
close(out);
}
return bytesOut.toByteArray();
}
public static final synchronized void setDataUpdateTime(Context context, long time, SharedPreferences pref) {
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent dataUpdate = new Intent(context, AutoDataUpdateReceiver.class);
dataUpdate.putExtra(SettingConstants.TIME_DATA_UPDATE_EXTRA, true);
if(time != 0) {
PendingIntent pending = PendingIntent.getBroadcast(context, DATA_UPDATE_KEY, dataUpdate, PendingIntent.FLAG_UPDATE_CURRENT);
CompatUtils.setAlarmInexact(alarmManager,AlarmManager.RTC_WAKEUP, Math.max(System.currentTimeMillis()+28*60000,time), pending);
}
}
public static final synchronized void removeDataUpdateTime(Context context, SharedPreferences pref) {
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent dataUpdate = new Intent(context, AutoDataUpdateReceiver.class);
PendingIntent pending = PendingIntent.getBroadcast(context, DATA_UPDATE_KEY, dataUpdate, PendingIntent.FLAG_NO_CREATE);
if(pending != null) {
alarmManager.cancel(pending);
}
}
public static final void setDataTableRefreshTime(Context context) {
Calendar now = Calendar.getInstance();
now.add(Calendar.DAY_OF_YEAR, 1);
now.set(Calendar.HOUR_OF_DAY, 0);
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 5);
now.set(Calendar.MILLISECOND, 0);
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent dataUpdate = new Intent(context, ServiceUpdateDataTable.class);
if(now.getTimeInMillis() < System.currentTimeMillis()) {
now.add(Calendar.SECOND, 10);
}
PendingIntent pending = PendingIntent.getService(context, REQUEST_CODE_DATA_TABLE_UPDATE, dataUpdate, PendingIntent.FLAG_UPDATE_CURRENT);
CompatUtils.setExactAlarmAndAllowWhileIdle(context, alarmManager,AlarmManager.RTC_WAKEUP, now.getTimeInMillis(), pending);
}
public static final synchronized void handleDataUpdatePreferences(Context context) {
handleDataUpdatePreferences(context,false);
}
public static final synchronized void handleDataUpdatePreferences(Context context, boolean fromNow) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
IOUtils.removeDataUpdateTime(context, pref);
if(PrefUtils.getStringValue(R.string.PREF_AUTO_UPDATE_TYPE, R.string.pref_auto_update_type_default).equals("2")) {
int days = Integer.parseInt(PrefUtils.getStringValue(R.string.PREF_AUTO_UPDATE_FREQUENCY, R.string.pref_auto_update_frequency_default)) + 1;
int time = PrefUtils.getIntValue(R.string.PREF_AUTO_UPDATE_START_TIME, R.integer.pref_auto_update_start_time_default);
long current = PrefUtils.getLongValue(R.string.AUTO_UPDATE_CURRENT_START_TIME, R.integer.auto_update_current_start_time_default);
if(current < System.currentTimeMillis()) {
long lastDate = PrefUtils.getLongValue(R.string.LAST_DATA_UPDATE, R.integer.last_data_update_default);
if(lastDate == 0) {
lastDate = (System.currentTimeMillis() - (24 * 60 * 60000));
Editor edit = pref.edit();
edit.putLong(context.getString(R.string.LAST_DATA_UPDATE), lastDate);
edit.commit();
}
if(fromNow) {
current = System.currentTimeMillis();
}
if(PrefUtils.getStringValue(R.string.PREF_EPGPAID_USER, "").trim().length() > 0 &&
PrefUtils.getStringValue(R.string.PREF_EPGPAID_PASSWORD, "").trim().length() > 0) {
Calendar test = Calendar.getInstance(TimeZone.getTimeZone("CET"));
test.set(Calendar.SECOND, 0);
test.set(Calendar.MILLISECOND, 0);
int timeTest = time;
do {
timeTest = time + ((int)(Math.random() * 6 * 60));
test.set(Calendar.HOUR_OF_DAY, timeTest/60);
test.set(Calendar.MINUTE, timeTest%60);
}while(test.get(Calendar.HOUR_OF_DAY) >= 23 || test.get(Calendar.HOUR_OF_DAY) < 4 ||
(test.get(Calendar.HOUR_OF_DAY) >= 15 && test.get(Calendar.HOUR_OF_DAY) < 17));
time = timeTest;
}
else {
time += ((int)(Math.random() * 6 * 60));
}
Calendar last = Calendar.getInstance();
last.setTimeInMillis(lastDate);
last.add(Calendar.DAY_OF_YEAR, days);
last.set(Calendar.HOUR_OF_DAY, time/60);
last.set(Calendar.MINUTE, time%60);
last.set(Calendar.SECOND, 0);
last.set(Calendar.MILLISECOND, 0);
if(last.getTimeInMillis() < System.currentTimeMillis()) {
last.set(Calendar.DAY_OF_YEAR, Calendar.getInstance().get(Calendar.DAY_OF_YEAR));
if(last.get(Calendar.YEAR) < Calendar.getInstance().get(Calendar.YEAR)) {
last.set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR));
}
}
current = last.getTimeInMillis();
if(current < System.currentTimeMillis()) {
current += (24 * 60 * 60000);
}
current = Math.max(System.currentTimeMillis()+30*60000, current);
Editor currentTime = PreferenceManager.getDefaultSharedPreferences(context).edit();
currentTime.putLong(context.getString(R.string.AUTO_UPDATE_CURRENT_START_TIME), current);
currentTime.commit();
}
IOUtils.setDataUpdateTime(context, current, pref);
}
}
public static final String[] getStringArrayFromList(ArrayList<String> list) {
if(list != null) {
return list.toArray(new String[list.size()]);
}
return null;
}
public static boolean isConnectedToServer(final String url, final int timeout) {
final AtomicBoolean isConnected = new AtomicBoolean(false);
new Thread("NETWORK CONNECTION CHECK THREAD") {
public void run() {
URLConnection connection = null;
try {
URL myUrl = new URL(url);
connection = myUrl.openConnection();
IOUtils.setConnectionTimeout(connection,timeout);
HttpURLConnection httpConnection = (HttpURLConnection)connection;
if(httpConnection != null) {
int responseCode = httpConnection.getResponseCode();
isConnected.set(responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_ACCEPTED
|| responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpsURLConnection.HTTP_SEE_OTHER);
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
disconnect(connection);
}
}
}.start();
Thread check = new Thread("WAITING FOR NETWORK CONNECTION THREAD") {
@Override
public void run() {
int count = 0;
while(!isConnected.get() && count++ <= (timeout / 100)) {
try {
sleep(100);
} catch (InterruptedException e) {}
}
}
};
check.start();
try {
check.join();
} catch (InterruptedException e) {}
return isConnected.get();
}
public static void setConnectionTimeoutDefault(URLConnection connection) {
setConnectionTimeout(connection, 10000);
}
public static void setConnectionTimeout(URLConnection connection, int timeout) {
connection.setReadTimeout(timeout);
connection.setConnectTimeout(timeout);
}
/**
* Normalize time of given Calendar to 2014-12-31 with the given time.
* <p>
* @param cal The Calendar to normalize.
* @param minutesAfterMidnight The minutes after midnight to use.
* @return The normalized Calendar.
*/
public static Calendar normalizeTime(Calendar cal, int minutesAfterMidnight) {
return normalizeTime(cal, minutesAfterMidnight, 0);
}
/**
* Normalize time of given Calendar to 2014-12-31 with the given time.
* <p>
* @param cal The Calendar to normalize.
* @param minutesAfterMidnight The minutes after midnight to use.
* @param seconds The seconds to use
* @return The normalized Calendar.
*/
public static Calendar normalizeTime(Calendar cal, int minutesAfterMidnight, int seconds) {
return normalizeTime(cal, minutesAfterMidnight / 60, minutesAfterMidnight % 60, seconds);
}
/**
* Normalize time of given Calendar to 2014-12-31 with the hourOfDay and minutes.
* <p>
* @param cal The Calendar to normalize.
* @param hourOfDay The hour of day to use.
* @param minutes The minutes to use.
* @return The normalized Calendar.
*/
public static Calendar normalizeTime(Calendar cal, int hourOfDay, int minutes, int seconds) {
cal.set(2014, Calendar.DECEMBER, 31, hourOfDay, minutes, seconds);
return cal;
}
/**
* Closes the given object and releases assigned resources.
* Calls are <code>null</code>-safe and idempotent.
*
* @param closeable the object to close.
* @see Closeable
*/
public static void close(final Closeable closeable) {
if (closeable != null) {
try {
// Needs reflection to be compatible with Android 4.0
Method close = closeable.getClass().getMethod("close");
if(close != null) {
close.invoke(closeable);
}
}
catch (final Exception ignored) {
// intentionally ignored
}
}
}
/**
* Closes the given database cursor, releases assigned resources, and makes the cursor invalid.
* A call to {@link Cursor#requery()} will not make the cursor valid again.
* Calls are <code>null</code>-safe and idempotent.
*
* @param cursor the {@link Cursor} to close.
* @see Cursor#close()
*/
public static void close(final Cursor cursor) {
if (cursor!=null && !cursor.isClosed()) {
cursor.close();
}
}
/**
* Disconnects the given connection and releases or reuses associated resources.
* Calls are <code>null</code>-safe and idempotent.
* <p/>
* Note: the underlying connection must be inherited from {@link HttpURLConnection}.
*
* @param connection the connection to release.
* @see HttpURLConnection#disconnect()
*/
public static void disconnect(final URLConnection connection) {
if (connection instanceof HttpURLConnection) {
try {
((HttpURLConnection) connection).disconnect();
} catch (final Exception ignored) {
// intentionally ignored
}
}
}
public static List<Channel> getChannelList(Context context) {
ArrayList<Channel> channelList = new ArrayList<Channel>();
if(IOUtils.isDatabaseAccessible(context)) {
final long token = Binder.clearCallingIdentity();
final Cursor channels = context.getContentResolver().query(TvBrowserContentProvider.CONTENT_URI_CHANNELS, new String[] {TvBrowserContentProvider.KEY_ID, TvBrowserContentProvider.CHANNEL_KEY_NAME, TvBrowserContentProvider.CHANNEL_KEY_LOGO}, TvBrowserContentProvider.CHANNEL_KEY_SELECTION, null, TvBrowserContentProvider.CHANNEL_KEY_ORDER_NUMBER + ", " + TvBrowserContentProvider.KEY_ID);
try {
if(IOUtils.prepareAccess(channels)) {
int keyColumn = channels.getColumnIndex(TvBrowserContentProvider.KEY_ID);
int nameColumn = channels.getColumnIndex(TvBrowserContentProvider.CHANNEL_KEY_NAME);
int iconColumn = channels.getColumnIndex(TvBrowserContentProvider.CHANNEL_KEY_LOGO);
while(channels.moveToNext()) {
channelList.add(new Channel(channels.getInt(keyColumn), channels.getString(nameColumn), channels.getBlob(iconColumn)));
}
}
}finally {
IOUtils.close(channels);
Binder.restoreCallingIdentity(token);
}
}
return channelList;
}
public static File getDownloadDirectory(Context context) {
File parent = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
boolean external = true;
if(parent == null || !parent.isDirectory()) {
external = false;
parent = context.getDir(Environment.DIRECTORY_DOWNLOADS, Context.MODE_PRIVATE);
}
File path = new File(parent,"tvbrowserdata");
File nomedia = new File(path,".nomedia");
if(!path.isDirectory()) {
if(!path.mkdirs() && external) {
parent = context.getDir(Environment.DIRECTORY_DOWNLOADS, Context.MODE_PRIVATE);
path = new File(parent,"tvbrowserdata");
nomedia = new File(path,".nomedia");
if(!path.isDirectory()) {
path.mkdirs();
}
}
}
if(!nomedia.isFile()) {
try {
nomedia.createNewFile();
} catch (IOException e) {}
}
return path;
}
/**
* Gets the color for the given encoded category as array with index 0
* containing the activated state of the color category 0 means disabled,
* 1 means activated. The value with index 1 contains the color.
* <p>
* @param encodedColor The color category preference value.
* @return An int array with the result.
*/
public static int[] getActivatedColorFor(String encodedColor) {
int[] result = new int[] {0,0};
if(encodedColor != null) {
if(encodedColor.contains(";")) {
String[] parts = encodedColor.split(";");
result[0] = Boolean.parseBoolean(parts[0]) ? 1 : 0;
result[1] = Integer.parseInt(parts[1]);
}
else {
result[0] = 1;
if(encodedColor.startsWith("#")) {
result[1] = (int)Long.parseLong(encodedColor.substring(1), 16);
}
else {
result[1] = Integer.parseInt(encodedColor);
}
}
}
return result;
}
public static final String getUniqueChannelKey(String groupKey, String channelKey) {
return new StringBuilder(groupKey.trim()).append("_##_").append(channelKey.trim()).toString();
}
public static final String[] getUniqueChannelKeyParts(String uniqueKey) {
return uniqueKey.split("_##_");
}
public static final boolean isInteractive(Context context) {
return CompatUtils.isInteractive((PowerManager)context.getSystemService(Context.POWER_SERVICE));
}
/**
* Decode the given value into an array of episode numbers.
* <p>
* @param fieldValue The field value to decode.
* @return An array with the contained episode numbers.
* @since 0.5.7.3
*/
public static Integer[] decodeSingleFieldValueToMultipleEpisodeNumers(int fieldValue) {
int encodingMask = (fieldValue >> 30) & 0x3;
if(encodingMask == 0) {
if(((fieldValue >> 29) & 0x1) == 0x1) {
int first = fieldValue & 0x3FFF;
int second = (fieldValue >> 14) & 0x7FFF;
return new Integer[] {first,second};
}
else {
return new Integer[] {fieldValue};
}
}
else {
int andMask = 0xFF;
int valueMask = 0x7F;
int shiftMask = 8;
int num = 2;
if(encodingMask == 2) {
andMask = 0x1F;
valueMask = 0xF;
shiftMask = 5;
num = 3;
}
else if(encodingMask == 3) {
andMask = 0xF;
valueMask = 0x7;
shiftMask = 4;
num = 4;
}
int last = fieldValue & 0x3FFF;
ArrayList<Integer> valueList = new ArrayList<Integer>();
valueList.add(last);
for(int i = 0; i < num; i++) {
int testValue = (fieldValue >> (14 + i * shiftMask)) & andMask;
int absValue = (testValue & valueMask) + 1;
if(((testValue >> shiftMask -1) & 0x1) == 0x1) {
absValue = absValue * -1;
}
last += absValue;
valueList.add(last);
}
return valueList.toArray(new Integer[valueList.size()]);
}
}
/**
* Decode the given value into a String of episode numbers.
* <p>
* @param fieldValue The field value to decode.
* @return A String of episode numbers.
* @since 0.5.7.3
*/
public static String decodeSingleFieldValueToMultipleEpisodeString(int fieldValue) {
Integer[] episodes = decodeSingleFieldValueToMultipleEpisodeNumers(fieldValue);
StringBuilder epis = new StringBuilder();
for(int episode : episodes) {
if(epis.length() > 0) {
epis.append("|");
}
epis.append(episode);
}
return epis.toString();
}
public static final void deleteOldData(Context context) {
Calendar cal2 = Calendar.getInstance();
cal2.add(Calendar.DAY_OF_YEAR, -2);
cal2.set(Calendar.HOUR_OF_DAY, 0);
cal2.set(Calendar.MINUTE, 0);
cal2.set(Calendar.SECOND, 0);
cal2.set(Calendar.MILLISECOND, 0);
long daysSince1970 = cal2.getTimeInMillis() / 24 / 60 / 60000;
try {
context.getContentResolver().delete(
TvBrowserContentProvider.CONTENT_URI_DATA,
TvBrowserContentProvider.DATA_KEY_STARTTIME + "<"
+ cal2.getTimeInMillis(), null);
} catch (IllegalArgumentException e) {
}
try {
context.getContentResolver().delete(
TvBrowserContentProvider.CONTENT_URI_DATA_VERSION,
TvBrowserContentProvider.VERSION_KEY_DAYS_SINCE_1970 + "<"
+ daysSince1970, null);
} catch (IllegalArgumentException e) {
}
final File pathBase = getDownloadDirectory(context);
if(pathBase.isDirectory()) {
final File epgPaidPath = new File(pathBase, "epgPaidData");
if(epgPaidPath.isDirectory()) {
final File fileSummaryCurrent = new File(epgPaidPath,"summary.gz");
final Properties currentProperties = readPropertiesFile(fileSummaryCurrent);
if(!currentProperties.isEmpty()) {
final long startMinute = cal2.getTimeInMillis() / 60000;
final File[] toDelete = pathBase.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
boolean result = false;
int index = file.getName().indexOf("_");
if(index > 0) {
try {
result = Long.parseLong(file.getName().substring(0,index)) < startMinute;
}catch(NumberFormatException nfe) {}
}
return result;
}
});
for(final File file : toDelete) {
if(file.getName().endsWith("_base.gz")) {
int index = file.getName().lastIndexOf("base.gz");
currentProperties.remove(file.getName().substring(0, index));
}
if(!file.delete()) {
file.deleteOnExit();
}
}
storeProperties(currentProperties, fileSummaryCurrent, "Properties of EPGpaid");
}
}
}
}
/**
* Sets the cursor to the first entry if it isn't <code>null</code> or has no rows.
* <p>
* @param cursor The cursor to move to the first entry, <code>null</code> value is possible.
* @return <code>true</code> if the cursor could be moved to the first entry,
* <code>false</code> otherwise.
*/
public static final boolean prepareAccessFirst(Cursor cursor) {
boolean result = false;
if(cursor != null && cursor.getCount() > 0 && !cursor.isClosed()) {
cursor.moveToFirst();
result = true;
}
return result;
}
public static final boolean prepareAccess(Cursor cursor) {
boolean result = false;
if(cursor != null && cursor.getCount() > 0 && !cursor.isClosed()) {
cursor.moveToPosition(-1);
result = true;
}
return result;
}
public static void removeReminder(Context context, long programID) {
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent remind = new Intent(context,ReminderBroadcastReceiver.class);
remind.putExtra(SettingConstants.REMINDER_PROGRAM_ID_EXTRA, programID);
PendingIntent pending = PendingIntent.getBroadcast(context, (int)programID, remind, PendingIntent.FLAG_NO_CREATE);
Logging.log(ReminderBroadcastReceiver.tag, " Delete reminder for programID '" + programID + "' with pending intent '" + pending + "'", Logging.TYPE_REMINDER, context);
if(pending != null) {
alarmManager.cancel(pending);
}
pending = PendingIntent.getBroadcast(context, (int)-programID, remind, PendingIntent.FLAG_NO_CREATE);
Logging.log(ReminderBroadcastReceiver.tag, " Delete reminder for programID '-" + programID + "' with pending intent '" + pending + "'", Logging.TYPE_REMINDER, context);
if(pending != null) {
alarmManager.cancel(pending);
}
}
public static final boolean isDatabaseAccessible(Context context) {
boolean result = true;
if(context != null) {
String path = PrefUtils.getSharedPreferences(PrefUtils.TYPE_PREFERENCES_SHARED_GLOBAL, context).getString(context.getString(R.string.PREF_DATABASE_PATH), context.getString(R.string.pref_database_path_default));
if(!path.equals(context.getString(R.string.pref_database_path_default))) {
result = new File(path).canWrite();
}
}
return result;
}
/**
* Copies the source file to the target. An existing target file
* will be overwritten.
*
* @param source The source file to copy
* @param target The target file.
* @return <code>true</code> if the file could be copied, <code>false</code> otherwise.
*/
public static final boolean copyFile(File source, File target) {
boolean result = false;
if(source.isFile()) {
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(source));
FileOutputStream fOut = new FileOutputStream(target);
fOut.getChannel().truncate(0);
out = new BufferedOutputStream(fOut);
byte temp[] = new byte[1024];
int count;
while ((count = in.read(temp, 0, 1024)) != -1) {
if(temp != null && count > 0) {
out.write(temp, 0, count);
}
}
out.flush();
result = target.length() == source.length();
} catch(IOException ioe) {
Log.d("info12", "", ioe);
} finally {
close(in);
close(out);
}
}
return result;
}
public static final boolean isBatterySufficient(Context context) {
boolean result = false;
if(context != null && context.getApplicationContext() != null) {
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.getApplicationContext().registerReceiver(null, filter);
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
result = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
if(!result) {
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
result = MIN_BATTERIE_LEVEL <= (level / (float)scale);
}
}
return result;
}
public static void storeProperties(Properties prop, File propertiesFile, String comment) {
if(propertiesFile.getParentFile().isDirectory()) {
GZIPOutputStream out = null;
try {
out = new GZIPOutputStream(new FileOutputStream(propertiesFile));
prop.store(out, comment);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
close(out);
}
}
}
public static Properties readPropertiesFile(File propertiesFile) {
final Properties properties = new Properties();
if(propertiesFile.isFile()) {
GZIPInputStream in = null;
try {
in = new GZIPInputStream(new FileInputStream(propertiesFile));
properties.load(in);
} catch(IOException e) {
} finally {
close(in);
}
}
return properties;
}
/**
* Will wait a maximum of waitInMilliseconds in a separate Thread before running
* the Runnable delayed. (Might not wait the full wait time if the sleeping Thread is interrupted.)
* <p>
* @param delayed The runnable to run after the wait time.
* @param waitInMilliseconds The time in milliseconds to wait.
*/
public static void postDelayedInSeparateThread(final Runnable delayed, final long waitInMilliseconds) {
postDelayedInSeparateThread("postDelayedInSeparateThread", delayed, waitInMilliseconds);
}
/**
* Will wait a maximum of waitInMilliseconds in a separate Thread before running
* the Runnable delayed. (Might not wait the full wait time if the sleeping Thread is interrupted.)
* <p>
* @param threadName The name of the waiting thread.
* @param delayed The runnable to run after the wait time.
* @param waitInMilliseconds The time in milliseconds to wait.
*/
public static void postDelayedInSeparateThread(String threadName, final Runnable delayed, final long waitInMilliseconds) {
new Thread(threadName) {
@Override
public void run() {
try {
sleep(waitInMilliseconds);
} catch (InterruptedException e) {
// simply ignore
}
delayed.run();
}
}.start();
}
}