Manage your torrents from your Android device
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
9.3 KiB

/*
* Copyright 2010-2024 Eric Kok et al.
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid 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 Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.transdroid.core.widget;
import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import org.androidannotations.annotations.EService;
import org.transdroid.R;
import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.ApplicationSettings_;
import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.app.settings.SystemSettings;
import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.lists.LocalTorrent;
import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.log.Log_;
import org.transdroid.core.service.ConnectivityHelper_;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentsComparator;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.util.FileSizeConverter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A service for the list widget to update the remote views that a list widget shows, by getting the torrents from the
* server (synchronously) and building {@link RemoteViews} objects for each torrent.
*
* @author Eric Kok
*/
@EService
public class ListWidgetViewsService extends RemoteViewsService {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new WidgetViewsFactory(this.getApplicationContext(), intent);
}
}
class WidgetViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private final Context context;
private final int appWidgetId;
private final Log log;
private List<Torrent> torrents = null;
private ListWidgetConfig config = null;
public WidgetViewsFactory(Context applicationContext, Intent intent) {
this.context = applicationContext;
this.appWidgetId =
intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
this.log = Log_.getInstance_(applicationContext);
}
@Override
public void onCreate() {
// Nothing to do here (wait for onDataSetChanged)
}
@Override
public void onDataSetChanged() {
// Load the widget settings
ApplicationSettings settings = ApplicationSettings_.getInstance_(context);
config = settings.getWidgetConfig(appWidgetId);
if (config == null || config.getServerId() < 0) {
log.e(context, "Looking for widget data while the widget wasn't yet configured");
return;
}
ServerSetting server = settings.getServerSetting(config.getServerId());
if (server == null) {
// TODO: Show error text some how in the remote view, perhaps via the EmptyView's text?
log.e(context, "The server for which this widget was created no longer exists");
if (torrents != null) {
torrents.clear();
}
return;
}
// Load the torrents; synchronously
IDaemonAdapter connection =
server.getServerAdapter(ConnectivityHelper_.getInstance_(context).getConnectedNetworkName(),
context);
DaemonTaskResult result = RetrieveTask.create(connection).execute(log);
if (!(result instanceof RetrieveTaskSuccessResult)) {
// TODO: Show error text somehow in the remote view, perhaps via the EmptyView's text?
log.e(context, "The torrents could not be retrieved at this time; probably a connection issue");
if (torrents != null) {
torrents.clear();
}
return;
}
// We have data; filter, sort and store it to use later when getViewAt gets called
SystemSettings systemSettings = SystemSettings_.getInstance_(context);
ArrayList<Torrent> filteredTorrents = new ArrayList<>();
List<Torrent> allTorrents = ((RetrieveTaskSuccessResult) result).getTorrents();
for (Torrent torrent : allTorrents) {
if (config.getStatusType().getFilterItem(context)
.matches(torrent, systemSettings.treatDormantAsInactive())) {
filteredTorrents.add(torrent);
}
}
if (filteredTorrents.size() > 0) {
// Only sort when there are actually torrents left after filtering
Daemon serverType = filteredTorrents.get(0).getDaemon();
Collections.sort(filteredTorrents,
new TorrentsComparator(serverType, config.getSortBy(), config.shouldReserveSort()));
}
torrents = filteredTorrents;
// If the user asked to show the server status statistics, we need to update the widget remote views again
RemoteViews rv = ListWidgetProvider.buildRemoteViews(context, appWidgetId, config);
if (config.shouldShowStatusView()) {
// Update the server status count and speeds in the 'action bar'
int downcount = 0, upcount = 0, downspeed = 0, upspeed = 0;
for (Torrent torrent : torrents) {
if (torrent.isDownloading(systemSettings.treatDormantAsInactive())) {
downcount++;
upcount++;
} else if (torrent.isSeeding(systemSettings.treatDormantAsInactive())) {
upcount++;
}
downspeed += torrent.getRateDownload();
upspeed += torrent.getRateUpload();
}
rv.setViewVisibility(R.id.navigation_view, View.GONE);
rv.setViewVisibility(R.id.serverstatus_view, View.VISIBLE);
rv.setTextViewText(R.id.downcount_text, Integer.toString(downcount));
rv.setTextViewText(R.id.upcount_text, Integer.toString(upcount));
rv.setTextViewText(R.id.downspeed_text, FileSizeConverter.getSize(downspeed) + "/s");
rv.setTextViewText(R.id.upspeed_text, FileSizeConverter.getSize(upspeed) + "/s");
AppWidgetManager.getInstance(context.getApplicationContext()).updateAppWidget(appWidgetId, rv);
}
}
@Override
public RemoteViews getViewAt(int position) {
// Load the dark or light widget list item layout xml
RemoteViews rv = new RemoteViews(context.getPackageName(),
config.shouldUseDarkTheme() ? R.layout.list_item_widget_dark : R.layout.list_item_widget);
// Bind the torrent details texts and status colour
Torrent torrent = torrents.get(position);
LocalTorrent local = LocalTorrent.fromTorrent(torrent);
rv.setTextViewText(R.id.name_text, torrent.getName());
rv.setTextViewText(R.id.progress_text, local.getProgressSizeText(context.getResources(), false));
rv.setTextViewText(R.id.ratio_text, local.getProgressEtaRatioText(context.getResources()));
int statusColour;
switch (torrent.getStatusCode()) {
case Downloading:
statusColour = R.color.torrent_downloading;
break;
case Paused:
statusColour = R.color.torrent_paused;
break;
case Seeding:
statusColour = R.color.torrent_seeding;
break;
case Error:
statusColour = R.color.torrent_error;
break;
default: // Checking, Waiting, Queued, Unknown
statusColour = R.color.torrent_other;
break;
}
rv.setInt(R.id.status_view, "setBackgroundColor", context.getResources().getColor(statusColour));
Intent startIntent = new Intent();
startIntent.putExtra(ListWidgetProvider.EXTRA_SERVER, config.getServerId());
startIntent.putExtra(ListWidgetProvider.EXTRA_TORRENT, torrent);
rv.setOnClickFillInIntent(R.id.widget_line_layout, startIntent);
return rv;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public void onDestroy() {
if (torrents != null) {
torrents.clear();
}
torrents = null;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public int getCount() {
if (torrents == null) {
return 0;
}
return torrents.size();
}
@Override
public long getItemId(int position) {
return position;
}
}