/*
* TV-Browser for Android
* Copyright (C) 2014 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.devplugin;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.tvbrowser.content.TvBrowserContentProvider;
import org.tvbrowser.settings.SettingConstants;
import org.tvbrowser.tvbrowser.Logging;
import org.tvbrowser.tvbrowser.R;
import org.tvbrowser.utils.IOUtils;
import org.tvbrowser.utils.PrefUtils;
import org.tvbrowser.utils.ProgramUtils;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.Cursor;
import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
/**
* A class that handles TV-Browser Plugins.
*
* @author René Mach
*/
public final class PluginHandler {
public static final String PLUGIN_ACTION = "org.tvbrowser.intent.action.PLUGIN";
private static ArrayList<PluginServiceConnection> PLUGIN_LIST;
private static PluginManager PLUGIN_MANAGER;
private static boolean RELOAD = true;
private static final AtomicInteger BLOG_COUNT = new AtomicInteger(0);
public static final boolean pluginsAvailable() {
return PLUGIN_LIST != null && !PLUGIN_LIST.isEmpty();
}
private static PluginManager createPluginManager(final Context context) {
return new PluginManager.Stub() {
@Override
public List<Channel> getSubscribedChannels() throws RemoteException {
return IOUtils.getChannelList(context);
}
@Override
public Program getProgramWithId(long programId) throws RemoteException {
Program result = null;
final long token = Binder.clearCallingIdentity();
if(IOUtils.isDatabaseAccessible(context)) {
Cursor programs = context.getContentResolver().query(ContentUris.withAppendedId(TvBrowserContentProvider.CONTENT_URI_DATA_WITH_CHANNEL,programId), ProgramUtils.DATA_CHANNEL_PROJECTION, null, null, null);
try {
result = ProgramUtils.createProgramFromDataCursor(context, programs);
}finally {
IOUtils.close(programs);
Binder.restoreCallingIdentity(token);
}
}
return result;
}
@Override
public Program getProgramForChannelAndTime(int channelId, long startTimeInUTC) throws RemoteException {
Program result = null;
String where = TvBrowserContentProvider.CHANNEL_KEY_CHANNEL_ID + "=" + channelId + " AND " + TvBrowserContentProvider.DATA_KEY_STARTTIME + "=" + startTimeInUTC;
final long token = Binder.clearCallingIdentity();
if(IOUtils.isDatabaseAccessible(context)) {
Cursor programs = context.getContentResolver().query(TvBrowserContentProvider.CONTENT_URI_DATA_WITH_CHANNEL, ProgramUtils.DATA_CHANNEL_PROJECTION, where, null, null);
try {
result = ProgramUtils.createProgramFromDataCursor(context, programs);
}finally {
IOUtils.close(programs);
Binder.restoreCallingIdentity(token);
}
}
return result;
}
@Override
public TvBrowserSettings getTvBrowserSettings() throws RemoteException {
String version = "Unknown";
int versionCode = -1;
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
version = pInfo.versionName;
versionCode = pInfo.versionCode;
} catch (NameNotFoundException e) {}
return new TvBrowserSettings(SettingConstants.IS_DARK_THEME, version, versionCode, PrefUtils.getLongValueWithDefaultKey(R.string.META_DATA_ID_FIRST_KNOWN, R.integer.meta_data_id_default), PrefUtils.getLongValueWithDefaultKey(R.string.META_DATA_ID_LAST_KNOWN, R.integer.meta_data_id_default), PrefUtils.getLongValue(R.string.PREF_LAST_KNOWN_DATA_DATE, SettingConstants.DATA_LAST_DATE_NO_DATA));
}
@Override
public boolean markProgram(Program program) throws RemoteException {
return program != null ? markProgramWithIcon(program, null) : false;
}
@Override
public boolean unmarkProgram(Program program) throws RemoteException {
return program != null ? unmarkProgramWithIcon(program, null) : false;
}
@Override
public boolean markProgramWithIcon(Program program, String pluginCanonicalClassName) throws RemoteException {
return program != null ? ProgramUtils.markProgram(context, program, pluginCanonicalClassName) : false;
}
@Override
public boolean unmarkProgramWithIcon(Program program, String pluginCanonicalClassName) throws RemoteException {
return program != null ? ProgramUtils.unmarkProgram(context, program, pluginCanonicalClassName) : false;
}
@Override
public Program[] getProgramsForChannelInRange(int channelId, long startTimeInUTC, long endTimeInUTC) throws RemoteException {
Program[] result = null;
StringBuilder where = new StringBuilder();
where.append(TvBrowserContentProvider.CHANNEL_KEY_CHANNEL_ID).append(" IS ").append(channelId);
where.append(" AND ");
where.append(TvBrowserContentProvider.DATA_KEY_STARTTIME).append(">=").append(startTimeInUTC);
where.append(" AND ");
where.append(TvBrowserContentProvider.DATA_KEY_ENDTIME).append("<=").append(endTimeInUTC);
final long token = Binder.clearCallingIdentity();
if(IOUtils.isDatabaseAccessible(context)) {
Cursor programs = context.getContentResolver().query(TvBrowserContentProvider.CONTENT_URI_DATA_WITH_CHANNEL, ProgramUtils.DATA_CHANNEL_PROJECTION, where.toString(), null, null);
try {
result = ProgramUtils.createProgramsFromDataCursor(context, programs);
}catch(Throwable t) {
RemoteException re = new RemoteException();
re.initCause(t);
throw re;
}finally {
IOUtils.close(programs);
Binder.restoreCallingIdentity(token);
}
}
return result;
}
@Override
public void setRatingForProgram(Program program, int rating) throws RemoteException {
// TODO Create rating function.
}
@Override
public Program[] getRunningProgramsForChannel(int channelId, long timeInUTC) throws RemoteException {
Program[] result = null;
StringBuilder where = new StringBuilder();
where.append(TvBrowserContentProvider.CHANNEL_KEY_CHANNEL_ID).append(" IS ").append(channelId);
where.append(" AND ");
where.append(TvBrowserContentProvider.DATA_KEY_STARTTIME).append("<=").append(timeInUTC);
where.append(" AND ");
where.append(TvBrowserContentProvider.DATA_KEY_ENDTIME).append(">=").append(timeInUTC);
final long token = Binder.clearCallingIdentity();
if(IOUtils.isDatabaseAccessible(context)) {
Cursor programs = context.getContentResolver().query(TvBrowserContentProvider.CONTENT_URI_DATA_WITH_CHANNEL, ProgramUtils.DATA_CHANNEL_PROJECTION, where.toString(), null, null);
try {
result = ProgramUtils.createProgramsFromDataCursor(context, programs);
}catch(Throwable t) {
RemoteException re = new RemoteException();
re.initCause(t);
throw re;
}finally {
IOUtils.close(programs);
Binder.restoreCallingIdentity(token);
}
}
return result;
}
};
}
public static PluginManager getPluginManagerCreateIfNecessary(Context context) {
if(PLUGIN_MANAGER == null) {
return createPluginManager(context);
}
return PLUGIN_MANAGER;
}
private static void doLog(Context context, String message) {
Logging.log(null, message, Logging.TYPE_PLUGIN, context);
}
public static final synchronized void loadPlugins(Context context1/*, Handler handler*/) {
try {
doLog(context1, "loadPlugins");
final Context context = context1.getApplicationContext();
PLUGIN_MANAGER = createPluginManager(context);
if(PLUGIN_LIST == null) {
PLUGIN_LIST = new ArrayList<PluginServiceConnection>();
RELOAD = true;
}
if(RELOAD) {
RELOAD = false;
// loadFirstAndLastProgramId(context);
ProgramUtils.handleFirstAndLastKnownProgramId(context, PrefUtils.getLongValueWithDefaultKey(R.string.META_DATA_ID_FIRST_KNOWN, R.integer.meta_data_id_default), PrefUtils.getLongValueWithDefaultKey(R.string.META_DATA_ID_LAST_KNOWN, R.integer.meta_data_id_default));
PackageManager packageManager = context.getPackageManager();
Intent baseIntent = new Intent( PluginHandler.PLUGIN_ACTION );
baseIntent.setFlags( Intent.FLAG_DEBUG_LOG_RESOLUTION );
List<ResolveInfo> list = packageManager.queryIntentServices(baseIntent, PackageManager.GET_RESOLVED_FILTER );
doLog(context, "ResoleInfo list size: " + list.size());
for( int i = 0 ; i < list.size() ; ++i ) {
ResolveInfo info = list.get( i );
ServiceInfo sinfo = info.serviceInfo;
doLog(context, "ServiceInfo for "+i+" "+sinfo);
if(sinfo != null) {
doLog(context, " sinfo: " + sinfo.packageName+" "+ sinfo.name);
PluginServiceConnection plugin = new PluginServiceConnection(sinfo.packageName, sinfo.name, context);
if(!PLUGIN_LIST.contains(plugin) && plugin.bindPlugin(context, null)) {
PLUGIN_LIST.add(plugin);
}
}
}
IOUtils.postDelayedInSeparateThread("LAST ID INFO DATE SAVE THREAD", new Runnable() {
@Override
public void run() {
if(PLUGIN_LIST != null) {
Collections.sort(PLUGIN_LIST);
PrefUtils.getSharedPreferences(PrefUtils.TYPE_PREFERENCES_SHARED_GLOBAL, context).edit().putInt(context.getString(R.string.PLUGIN_LAST_ID_INFO_DATE), Calendar.getInstance().get(Calendar.DAY_OF_YEAR)).commit();
}
}
}, 2000);
}
incrementBlogCountIfZero();
doLog(context1, "Plugin reference count " + BLOG_COUNT.get());
}catch(Throwable t) {
}
}
public static PluginServiceConnection[] onlyLoadAndGetPlugins(Context context, Handler handler) {
final ArrayList<PluginServiceConnection> pluginList = new ArrayList<PluginServiceConnection>();
PackageManager packageManager = context.getPackageManager();
Intent baseIntent = new Intent( PluginHandler.PLUGIN_ACTION );
baseIntent.setFlags( Intent.FLAG_DEBUG_LOG_RESOLUTION );
List<ResolveInfo> list = packageManager.queryIntentServices(baseIntent, PackageManager.GET_RESOLVED_FILTER );
for( int i = 0 ; i < list.size() ; ++i ) {
ResolveInfo info = list.get( i );
ServiceInfo sinfo = info.serviceInfo;
if(sinfo != null) {
PluginServiceConnection plugin = new PluginServiceConnection(sinfo.packageName, sinfo.name, context);
if(plugin.bindPlugin(context, null)) {
pluginList.add(plugin);
}
}
}
IOUtils.postDelayedInSeparateThread("SORT PLUGINS WAITING THREAD", new Runnable() {
@Override
public void run() {
if(pluginList != null) {
Collections.sort(pluginList);
}
}
}, 2000);
return pluginList.toArray(new PluginServiceConnection[pluginList.size()]);
}
public static final void shutdownPlugins(Context context) {
doLog(context, "shutdownPlugins: reference count " + BLOG_COUNT.get());
if(BLOG_COUNT.get() == 1) {
doLog(context, "do Plugin shutdown");
if(PLUGIN_LIST != null) {
context = context.getApplicationContext();
for(PluginServiceConnection plugin : PLUGIN_LIST) {
if(plugin.isActivated()) {
plugin.callOnDeactivation();
}
plugin.unbindPlugin(context);
}
PLUGIN_LIST.clear();
PLUGIN_LIST = null;
}
PLUGIN_MANAGER = null;
PluginHandler.PLUGIN_LIST = null;
decrementBlogCount();
}
}
public static PluginManager getPluginManager() {
return PLUGIN_MANAGER;
}
public static boolean isMarkedByPlugins(long programId) {
boolean result = false;
if(PLUGIN_LIST != null) {
for(PluginServiceConnection connection : PLUGIN_LIST) {
try {
if(connection.isActivated() && connection.getPlugin().isMarked(programId)) {
result = true;
break;
}
} catch (RemoteException e) {}
}
}
return result;
}
public static boolean firstAndLastProgramIdAlreadyHandled() {
return Calendar.getInstance().get(Calendar.DAY_OF_YEAR) == PrefUtils.getIntValue(R.string.PLUGIN_LAST_ID_INFO_DATE, 0);
}
public static PluginServiceConnection getConnectionForId(String id) {
PluginServiceConnection result = null;
if(PLUGIN_LIST != null) {
for(PluginServiceConnection connection : PLUGIN_LIST) {
if(connection.getId().equals(id)) {
result = connection;
break;
}
}
}
return result;
}
public static boolean hasPlugins() {
return PLUGIN_LIST != null && !PLUGIN_LIST.isEmpty();
}
public static PluginServiceConnection[] getAvailablePlugins() {
PluginServiceConnection[] result = null;
if(hasPlugins()) {
result = PLUGIN_LIST.toArray(new PluginServiceConnection[PLUGIN_LIST.size()]);
}
return result;
}
static void removePluginServiceConnection(PluginServiceConnection connection) {
if(PluginHandler.PLUGIN_LIST != null) {
PluginHandler.PLUGIN_LIST.remove(connection);
RELOAD = true;
}
}
public static void incrementBlogCountIfZero() {
if(BLOG_COUNT.get() == 0) {
BLOG_COUNT.incrementAndGet();
}
}
public static void incrementBlogCount() {
BLOG_COUNT.incrementAndGet();
}
public static void decrementBlogCount() {
BLOG_COUNT.decrementAndGet();
}
}