Java tutorial
/* * Copyright (C) 2016 Yaroslav Pronin <proninyaroslav@mail.ru> * * This file is part of LibreTorrent. * * LibreTorrent 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. * * LibreTorrent 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 LibreTorrent. If not, see <http://www.gnu.org/licenses/>. */ package org.proninyaroslav.libretorrent; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.format.Formatter; import android.util.Log; import android.widget.Toast; import com.frostwire.jlibtorrent.AnnounceEntry; import com.frostwire.jlibtorrent.PeerInfo; import com.frostwire.jlibtorrent.Priority; import com.frostwire.jlibtorrent.SettingsPack; import com.frostwire.jlibtorrent.TorrentInfo; import com.frostwire.jlibtorrent.TorrentStatus; import com.frostwire.jlibtorrent.swig.settings_pack; import net.grandcentrix.tray.core.OnTrayPreferenceChangeListener; import net.grandcentrix.tray.core.TrayItem; import org.proninyaroslav.libretorrent.core.EngineTask; import org.proninyaroslav.libretorrent.core.Torrent; import org.proninyaroslav.libretorrent.core.TorrentDownload; import org.proninyaroslav.libretorrent.core.TorrentEngine; import org.proninyaroslav.libretorrent.core.TorrentEngineCallback; import org.proninyaroslav.libretorrent.core.TorrentMetaInfo; import org.proninyaroslav.libretorrent.core.TorrentStateCode; import org.proninyaroslav.libretorrent.core.TorrentTaskServiceIPC; import org.proninyaroslav.libretorrent.core.exceptions.DecodeException; import org.proninyaroslav.libretorrent.core.exceptions.FileAlreadyExistsException; import org.proninyaroslav.libretorrent.core.stateparcel.PeerStateParcel; import org.proninyaroslav.libretorrent.core.stateparcel.StateParcelCache; import org.proninyaroslav.libretorrent.core.stateparcel.TorrentStateParcel; import org.proninyaroslav.libretorrent.core.stateparcel.TrackerStateParcel; import org.proninyaroslav.libretorrent.core.storage.TorrentStorage; import org.proninyaroslav.libretorrent.core.utils.TorrentUtils; import org.proninyaroslav.libretorrent.core.utils.Utils; import java.io.File; import java.io.FileNotFoundException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; public class TorrentTaskService extends Service implements TorrentEngineCallback, OnTrayPreferenceChangeListener { @SuppressWarnings("unused") private static final String TAG = TorrentTaskService.class.getSimpleName(); private static final int SERVICE_STARTED_NOTIFICATION_ID = 1; private static final int TORRENTS_MOVED_NOTIFICATION_ID = 2; private static final int SYNC_TIME = 1000; /* ms */ private boolean isAlreadyRunning; private NotificationManager notifyManager; /* For the pause action button of foreground notify */ Handler updateForegroundNotifyHandler; NotificationCompat.Builder foregroundNotify; /* TorrentEngine task */ EngineTask engineTask; ExecutorService exec; /* Tasks list */ private ConcurrentHashMap<String, TorrentDownload> torrentTasks = new ConcurrentHashMap<>(); /* IPC communication */ private Messenger messenger = new Messenger(new CallbackHandler(this)); /* List of connected clients */ private List<Messenger> clientCallbacks = new ArrayList<>(); private TorrentTaskServiceIPC ipc = new TorrentTaskServiceIPC(); private ReentrantLock sync; private TorrentStorage repo; private SettingsManager pref; private PowerManager.WakeLock wakeLock; /* Pause torrents (including new added) when in power settings are set power save flags */ private AtomicBoolean pauseTorrents = new AtomicBoolean(false); /* Reduces sending packets due skip cache duplicates */ private StateParcelCache<TorrentStateParcel> stateCache = new StateParcelCache<>(); private AtomicBoolean needsUpdateNotify; private Integer torrentsMoveTotal; private List<String> torrentsMoveSuccess; private List<String> torrentsMoveFailed; private boolean shutdownAfterMove = false; private boolean isNetworkOnline = false; public TorrentTaskService() { exec = Executors.newSingleThreadExecutor(); sync = new ReentrantLock(); needsUpdateNotify = new AtomicBoolean(false); } @Override public void onCreate() { super.onCreate(); Log.i(TAG, "Start " + TorrentTaskService.class.getSimpleName()); notifyManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); repo = new TorrentStorage(getApplicationContext()); pref = new SettingsManager(getApplicationContext()); pref.registerOnTrayPreferenceChangeListener(this); if (pref.getBoolean(getString(R.string.pref_key_cpu_do_not_sleep), false)) { setKeepCpuAwake(true); } engineTask = new EngineTask(getApplicationContext(), this); exec.execute(engineTask); } @Override public void onDestroy() { super.onDestroy(); setKeepCpuAwake(false); /* Handles must be destructed before the session is destructed */ torrentTasks.clear(); if (engineTask != null) { engineTask.cancel(); } isAlreadyRunning = false; repo = null; pref.unregisterOnTrayPreferenceChangeListener(this); pref = null; Log.i(TAG, "Stop " + TorrentTaskService.class.getSimpleName()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { ArrayList<String> ids = intent.getStringArrayListExtra(TorrentTaskServiceIPC.TAG_TORRENT_IDS_LIST); if (ids != null) { List<Torrent> torrents = new ArrayList<>(); for (String id : ids) { torrents.add(repo.getTorrentByID(id)); } addTorrents(torrents); } } if (isAlreadyRunning) { if (intent != null && intent.getAction() != null) { switch (intent.getAction()) { case NotificationReceiver.NOTIFY_ACTION_SHUTDOWN_APP: ipc.sendTerminateAllClients(clientCallbacks); clientCallbacks.clear(); stopForeground(true); stopSelf(startId); break; } } return START_STICKY; } /* The first start */ isAlreadyRunning = true; makeForegroundNotify(); startUpdateForegroundNotify(); return START_STICKY; } @Override public void onTaskRemoved(Intent rootIntent) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass()); restartServiceIntent.setPackage(getPackageName()); PendingIntent restartServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT); AlarmManager alarmService = (AlarmManager) getApplicationContext() .getSystemService(Context.ALARM_SERVICE); alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000, restartServicePendingIntent); } } @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); } @Override public void onTorrentAdded(String id, TorrentDownload task) { if (pauseTorrents.get()) { task.pause(); } torrentTasks.put(id, task); } @Override public void onEngineStarted() { // TODO: 13.10.17 !!! // if (torrentTasks.isEmpty()) { // List<Torrent> torrents = repo.getAll(); // loadTorrents(torrents); // } /* Set current port */ pref.put(getString(R.string.pref_key_port), engineTask.getEngine().getPort()); } @Override public void onEngineInterrupted() { torrentTasks.clear(); /* Reset the notification to the default template without torrents list */ makeForegroundNotify(); } @Override public void onTorrentStateChanged(String id) { sendTorrentState(torrentTasks.get(id)); } @Override public void onTorrentRemoved(String id) { torrentTasks.remove(id); } @Override public void onTorrentPaused(String id) { sendTorrentState(torrentTasks.get(id)); Torrent torrent = repo.getTorrentByID(id); if (torrent == null) { return; } if (!torrent.isPaused()) { torrent.setPaused(true); repo.update(torrent); torrent = repo.getTorrentByID(id); TorrentDownload task = torrentTasks.get(id); if (task != null && torrent != null) { task.setTorrent(torrent); } } } @Override public void onTorrentResumed(String id) { TorrentDownload task = torrentTasks.get(id); if (task != null && !task.getTorrent().isPaused()) { return; } Torrent torrent = repo.getTorrentByID(id); if (torrent == null) { return; } torrent.setPaused(false); repo.update(torrent); torrent = repo.getTorrentByID(id); if (task != null && torrent != null) { task.setTorrent(torrent); } } @Override public void onTorrentFinished(String id) { Torrent torrent = repo.getTorrentByID(id); if (torrent == null) { return; } if (!torrent.isFinished()) { torrent.setFinished(true); makeFinishNotify(torrent); repo.update(torrent); torrent = repo.getTorrentByID(id); TorrentDownload task = torrentTasks.get(id); if (task != null && torrent != null) { task.setTorrent(torrent); } if (pref.getBoolean(getString(R.string.pref_key_shutdown_downloads_complete), false)) { if (torrentsMoveTotal != null) { shutdownAfterMove = true; } else { Intent shutdownIntent = new Intent(getApplicationContext(), NotificationReceiver.class); shutdownIntent.setAction(NotificationReceiver.NOTIFY_ACTION_SHUTDOWN_APP); sendBroadcast(shutdownIntent); } } } } @Override public void onTorrentMoved(String id, boolean success) { TorrentDownload task = torrentTasks.get(id); String name = null; if (task != null) { name = task.getTorrent().getName(); } if (success) { if (torrentsMoveSuccess != null && name != null) { torrentsMoveSuccess.add(name); } } else { if (torrentsMoveFailed != null && name != null) { torrentsMoveFailed.add(name); } } if (torrentsMoveSuccess != null && torrentsMoveFailed != null) { if ((torrentsMoveSuccess.size() + torrentsMoveFailed.size()) == torrentsMoveTotal) { makeTorrentsMoveNotify(); if (shutdownAfterMove) { Intent shutdownIntent = new Intent(getApplicationContext(), NotificationReceiver.class); shutdownIntent.setAction(NotificationReceiver.NOTIFY_ACTION_SHUTDOWN_APP); sendBroadcast(shutdownIntent); } } } } @Override public void onIpFilterParsed(boolean success) { if (!success) { Toast.makeText(getApplicationContext(), getString(R.string.ip_filter_add_error), Toast.LENGTH_LONG) .show(); } } @Override public void onTrayPreferenceChanged(Collection<TrayItem> items) { if (engineTask.getEngine() == null || pref == null) { return; } for (TrayItem item : items) { if (item.module().equals(SettingsManager.MODULE_NAME)) { if (item.key().equals(getString(R.string.pref_key_max_download_speed))) { engineTask.getEngine().setDownloadSpeedLimit(pref.getInt(item.key(), 0)); } else if (item.key().equals(getString(R.string.pref_key_max_upload_speed))) { engineTask.getEngine().setUploadSpeedLimit(pref.getInt(item.key(), 0)); } else if (item.key().equals(getString(R.string.pref_key_cpu_do_not_sleep))) { setKeepCpuAwake(pref.getBoolean(getString(R.string.pref_key_cpu_do_not_sleep), false)); } else if (item.key().equals(getString(R.string.pref_key_enable_dht))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { sp.enableDht(pref.getBoolean(getString(R.string.pref_key_enable_dht), TorrentEngine.DEFAULT_DHT_ENABLED)); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_enable_lsd))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { sp.broadcastLSD(pref.getBoolean(getString(R.string.pref_key_enable_lsd), TorrentEngine.DEFAULT_LSD_ENABLED)); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_enable_utp))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { boolean enable = pref.getBoolean(getString(R.string.pref_key_enable_utp), TorrentEngine.DEFAULT_UTP_ENABLED); sp.setBoolean(settings_pack.bool_types.enable_incoming_utp.swigValue(), enable); sp.setBoolean(settings_pack.bool_types.enable_outgoing_utp.swigValue(), enable); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_enable_upnp))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { sp.setBoolean(settings_pack.bool_types.enable_upnp.swigValue(), pref.getBoolean( getString(R.string.pref_key_enable_upnp), TorrentEngine.DEFAULT_UPNP_ENABLED)); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_enable_natpmp))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { sp.setBoolean(settings_pack.bool_types.enable_natpmp.swigValue(), pref.getBoolean( getString(R.string.pref_key_enable_natpmp), TorrentEngine.DEFAULT_NATPMP_ENABLED)); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_enc_mode))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { int state = getEncryptMode(); sp.setInteger(settings_pack.int_types.in_enc_policy.swigValue(), state); sp.setInteger(settings_pack.int_types.out_enc_policy.swigValue(), state); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_enc_in_connections))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { int state = settings_pack.enc_policy.pe_disabled.swigValue(); if (pref.getBoolean(getString(R.string.pref_key_enc_in_connections), TorrentEngine.DEFAULT_ENCRYPT_IN_CONNECTIONS)) { state = getEncryptMode(); } sp.setInteger(settings_pack.int_types.in_enc_policy.swigValue(), state); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_enc_out_connections))) { SettingsPack sp = engineTask.getEngine().getSettings(); if (sp != null) { int state = settings_pack.enc_policy.pe_disabled.swigValue(); if (pref.getBoolean(getString(R.string.pref_key_enc_out_connections), TorrentEngine.DEFAULT_ENCRYPT_OUT_CONNECTIONS)) { state = getEncryptMode(); } sp.setInteger(settings_pack.int_types.out_enc_policy.swigValue(), state); engineTask.getEngine().setSettings(sp); } } else if (item.key().equals(getString(R.string.pref_key_use_random_port))) { if (pref.getBoolean(getString(R.string.pref_key_use_random_port), false)) { engineTask.getEngine().setRandomPort(); } else { engineTask.getEngine().setPort( pref.getInt(getString(R.string.pref_key_port), TorrentEngine.DEFAULT_PORT)); } } else if (item.key().equals(getString(R.string.pref_key_port))) { engineTask.getEngine() .setPort(pref.getInt(getString(R.string.pref_key_port), TorrentEngine.DEFAULT_PORT)); } } } } private int getEncryptMode() { int mode = pref.getInt(getString(R.string.pref_key_enc_mode), Integer.parseInt(getString(R.string.pref_enc_mode_prefer_value))); if (mode == Integer.parseInt(getString(R.string.pref_enc_mode_prefer_value))) { return settings_pack.enc_policy.pe_enabled.swigValue(); } else if (mode == Integer.parseInt(getString(R.string.pref_enc_mode_require_value))) { return settings_pack.enc_policy.pe_forced.swigValue(); } else { return settings_pack.enc_policy.pe_disabled.swigValue(); } } private void loadTorrents(Collection<Torrent> torrents) { if (torrents == null || engineTask == null) { return; } for (Torrent torrent : torrents) { if (!TorrentUtils.torrentDataExists(getApplicationContext(), torrent.getId())) { Log.e(TAG, "Torrent doesn't exists: " + torrent.getName()); repo.delete(torrent); } } engineTask.getEngine().restoreDownloads(torrents); } private void addTorrent(Torrent torrent) { if (torrent == null || engineTask == null) { return; } if (!TorrentUtils.torrentDataExists(getApplicationContext(), torrent.getId())) { Log.e(TAG, "Torrent doesn't exists: " + torrent.getName()); repo.delete(torrent); return; } if (torrentTasks.containsKey(torrent.getId())) { TorrentDownload task = torrentTasks.get(torrent.getId()); if (task != null) { task.remove(false); } torrentTasks.remove(torrent.getId()); } engineTask.getEngine().download(torrent); } private void addTorrents(Collection<Torrent> torrents) { if (torrents == null || engineTask == null) { return; } for (Torrent torrent : torrents) { if (!TorrentUtils.torrentDataExists(getApplicationContext(), torrent.getId())) { Log.e(TAG, "Torrent doesn't exists: " + torrent.getName()); repo.delete(torrent); continue; } if (torrentTasks.containsKey(torrent.getId())) { TorrentDownload task = torrentTasks.get(torrent.getId()); if (task != null) { task.remove(false); } torrentTasks.remove(torrent.getId()); } engineTask.getEngine().download(torrent); } } private void deleteTorrents(ArrayList<String> ids, boolean withFiles) { sync.lock(); try { if (ids != null) { for (String id : ids) { torrentTasks.get(id).remove(withFiles); repo.delete(id); } } needsUpdateNotify.set(true); sendTorrentsStateOneShot(); } catch (Exception e) { /* Ignore */ } finally { sync.unlock(); } } private void setTorrentName(String id, String name) { if (id == null || name == null) { return; } Torrent torrent = repo.getTorrentByID(id); if (torrent == null) { return; } torrent.setName(name); repo.update(torrent); torrent = repo.getTorrentByID(id); TorrentDownload task = torrentTasks.get(id); if (task != null && torrent != null) { task.setTorrent(torrent); needsUpdateNotify.set(true); sendTorrentsStateOneShot(); } } private void moveTorrent(Torrent torrent) { if (torrent == null) { return; } repo.update(torrent); torrent = repo.getTorrentByID(torrent.getId()); TorrentDownload task = torrentTasks.get(torrent.getId()); if (task != null) { if (torrentsMoveSuccess == null) { torrentsMoveSuccess = new ArrayList<>(); } if (torrentsMoveFailed == null) { torrentsMoveFailed = new ArrayList<>(); } if (torrentsMoveTotal == null) { torrentsMoveTotal = 0; } ++torrentsMoveTotal; task.setTorrent(torrent); task.setDownloadPath(torrent.getDownloadPath()); } } private void setTorrentDownloadPath(ArrayList<String> ids, String path) { if (ids == null || path == null || TextUtils.isEmpty(path)) { return; } for (String id : ids) { Torrent torrent = repo.getTorrentByID(id); if (torrent == null) { return; } torrent.setDownloadPath(path); repo.update(torrent); torrent = repo.getTorrentByID(id); TorrentDownload task = torrentTasks.get(id); if (task != null && torrent != null) { if (torrentsMoveSuccess == null) { torrentsMoveSuccess = new ArrayList<>(); } if (torrentsMoveFailed == null) { torrentsMoveFailed = new ArrayList<>(); } if (torrentsMoveTotal == null) { torrentsMoveTotal = 0; } ++torrentsMoveTotal; task.setTorrent(torrent); task.setDownloadPath(path); } } } private void setSequentialDownload(String id, boolean sequential) { if (id == null) { return; } Torrent torrent = repo.getTorrentByID(id); if (torrent == null) { return; } torrent.setSequentialDownload(sequential); repo.update(torrent); torrent = repo.getTorrentByID(id); TorrentDownload task = torrentTasks.get(id); if (task != null && torrent != null) { task.setTorrent(torrent); task.setSequentialDownload(sequential); } } private void changeFilesPriority(String id, ArrayList<Integer> priorities) { if (id == null || (priorities == null || priorities.size() == 0)) { return; } Torrent torrent = repo.getTorrentByID(id); if (torrent == null) { return; } torrent.setFilePriorities(priorities); repo.update(torrent); torrent = repo.getTorrentByID(id); TorrentDownload task = torrentTasks.get(id); if (task != null && torrent != null) { task.setTorrent(torrent); Priority[] list = new Priority[priorities.size()]; for (int i = 0; i < priorities.size(); i++) { list[i] = Priority.fromSwig(priorities.get(i)); } task.prioritizeFiles(list); } } private void replaceTrackers(String id, ArrayList<String> urls) { if (id == null || urls == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task != null) { task.replaceTrackers(new HashSet<>(urls)); } } private void addTrackers(String id, ArrayList<String> urls) { if (id == null || urls == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task != null) { task.addTrackers(new HashSet<>(urls)); } } /* * if id is null set global limit */ private void setUploadSpeedLimit(String id, int limit) { if (id == null && engineTask != null) { engineTask.getEngine().setUploadSpeedLimit(limit); } else { TorrentDownload task = torrentTasks.get(id); if (task != null) { task.setUploadSpeedLimit(limit); } } } /* * if id is null set global limit */ private void setDownloadSpeedLimit(String id, int limit) { if (id == null && engineTask != null) { engineTask.getEngine().setDownloadSpeedLimit(limit); } else { TorrentDownload task = torrentTasks.get(id); if (task != null) { task.setDownloadSpeedLimit(limit); } } } private void pauseAll() { for (TorrentDownload task : torrentTasks.values()) { if (task == null) { continue; } task.pause(); } } private void resumeAll() { for (TorrentDownload task : torrentTasks.values()) { if (task == null) { continue; } task.resume(); } } private void setKeepCpuAwake(boolean enable) { if (enable) { if (wakeLock == null) { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); } if (!wakeLock.isHeld()) { wakeLock.acquire(); } } else { if (wakeLock == null) { return; } if (wakeLock.isHeld()) { wakeLock.release(); } } } private TorrentStateParcel makeTorrentStateParcel(TorrentDownload task) { if (task == null) { return null; } Torrent torrent = task.getTorrent(); return new TorrentStateParcel(torrent.getId(), torrent.getName(), task.getStateCode(), task.getProgress(), task.getTotalReceivedBytes(), task.getTotalSentBytes(), task.getTotalWanted(), task.getDownloadSpeed(), task.getUploadSpeed(), task.getETA(), task.getFilesReceivedBytes(), task.getTotalSeeds(), task.getConnectedSeeds(), task.getTotalPeers(), task.getConnectedPeers(), task.getNumDownloadedPieces(), task.getShareRatio(), task.isReadyForPlaing()); } private ArrayList<TrackerStateParcel> makeTrackerStateParcelList(TorrentDownload task) { if (task == null || engineTask == null) { return null; } List<AnnounceEntry> trackers = task.getTrackers(); ArrayList<TrackerStateParcel> states = new ArrayList<>(); int statusDHT = TrackerStateParcel.Status.NOT_WORKING; int statusLSD = TrackerStateParcel.Status.NOT_WORKING; int statusPeX = TrackerStateParcel.Status.NOT_WORKING; if (engineTask.getEngine().isDHTEnabled()) { statusDHT = TrackerStateParcel.Status.WORKING; } if (engineTask.getEngine().isLSDEnabled()) { statusLSD = TrackerStateParcel.Status.WORKING; } if (engineTask.getEngine().isPeXEnabled()) { statusPeX = TrackerStateParcel.Status.WORKING; } states.add(new TrackerStateParcel(TrackerStateParcel.DHT_ENTRY_NAME, "", -1, statusDHT)); states.add(new TrackerStateParcel(TrackerStateParcel.LSD_ENTRY_NAME, "", -1, statusLSD)); states.add(new TrackerStateParcel(TrackerStateParcel.PEX_ENTRY_NAME, "", -1, statusPeX)); for (AnnounceEntry entry : trackers) { String url = entry.url(); /* Prevent duplicate */ if (url.equals(TrackerStateParcel.DHT_ENTRY_NAME) || url.equals(TrackerStateParcel.LSD_ENTRY_NAME) || url.equals(TrackerStateParcel.PEX_ENTRY_NAME)) { continue; } states.add(new TrackerStateParcel(entry)); } return states; } private ArrayList<PeerStateParcel> makePeerStateParcellList(TorrentDownload task) { if (task == null) { return null; } ArrayList<PeerStateParcel> states = new ArrayList<>(); ArrayList<PeerInfo> peers = task.getPeers(); TorrentStatus status = task.getTorrentStatus(); for (PeerInfo peer : peers) { PeerStateParcel state = new PeerStateParcel(peer.swig(), status); states.add(state); } return states; } private void sendTorrentsStateOneShot(Messenger replyTo) { if (replyTo == null) { return; } Bundle statesList = new Bundle(); for (TorrentDownload task : torrentTasks.values()) { if (task != null) { TorrentStateParcel state = makeTorrentStateParcel(task); if (!stateCache.contains(state)) { stateCache.put(state); } statesList.putParcelable(state.torrentId, state); } } try { ipc.sendTorrentStateOneShot(replyTo, statesList); } catch (RemoteException e) { /* The client is dead, ignore */ } } private void sendTorrentsStateOneShot() { Bundle statesList = new Bundle(); for (TorrentDownload task : torrentTasks.values()) { if (task != null) { TorrentStateParcel state = makeTorrentStateParcel(task); if (!stateCache.contains(state)) { stateCache.put(state); } statesList.putParcelable(state.torrentId, state); } } for (int i = clientCallbacks.size() - 1; i >= 0; i--) { try { ipc.sendTorrentStateOneShot(clientCallbacks.get(i), statesList); } catch (RemoteException e) { /* * The client is dead. Remove it from the list; * we are going through the list from back to front so this is safe to do inside the loop. */ clientCallbacks.remove(i); } } } private void sendTorrentsList() { Bundle statesList = new Bundle(); for (TorrentDownload task : torrentTasks.values()) { Torrent torrent = task.getTorrent(); if (torrent != null) { TorrentStateParcel state = new TorrentStateParcel(torrent.getId(), torrent.getName()); statesList.putParcelable(state.torrentId, state); } } for (int i = clientCallbacks.size() - 1; i >= 0; i--) { try { ipc.sendTorrentStateOneShot(clientCallbacks.get(i), statesList); } catch (RemoteException e) { /* * The client is dead. Remove it from the list; * we are going through the list from back to front so this is safe to do inside the loop. */ clientCallbacks.remove(i); } } } private void sendTorrentState(TorrentDownload task) { if (task == null) { return; } Torrent torrent = task.getTorrent(); if (torrent == null) { return; } TorrentStateParcel state = makeTorrentStateParcel(task); if (stateCache.contains(state)) { return; } else { stateCache.put(state); /* Update foreground notification only if added a new package to the cache */ needsUpdateNotify.set(true); } for (int i = clientCallbacks.size() - 1; i >= 0; i--) { try { ipc.sendUpdateState(clientCallbacks.get(i), state); } catch (RemoteException e) { /* * The client is dead. Remove it from the list; * we are going through the list from back to front so this is safe to do inside the loop. */ clientCallbacks.remove(i); } } } private void sendTorrentInfo(String id, Messenger replyTo) { if (id == null || replyTo == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task == null) { return; } TorrentInfo ti = task.getTorrentInfo(); TorrentMetaInfo info = null; try { info = new TorrentMetaInfo(ti); } catch (DecodeException e) { Log.e(TAG, "Can't decode torrent info: "); Log.e(TAG, Log.getStackTraceString(e)); } try { ipc.sendGetTorrentInfo(replyTo, info); } catch (RemoteException e) { /* The client is dead, ignore */ } } private void sendOnAddTorrents(ArrayList<TorrentStateParcel> states, ArrayList<Throwable> exceptions) { for (int i = clientCallbacks.size() - 1; i >= 0; i--) { try { ipc.sendTorrentsAdded(clientCallbacks.get(i), states, exceptions); } catch (RemoteException e) { /* * The client is dead. Remove it from the list; * we are going through the list from back to front so this is safe to do inside the loop. */ clientCallbacks.remove(i); } } } private void sendActiveAndSeedingTime(String id, Messenger replyTo) { if (id == null || replyTo == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task == null) { return; } try { ipc.sendGetActiveAndSeedingTime(replyTo, task.getActiveTime(), task.getSeedingTime()); } catch (RemoteException e) { /* The client is dead, ignore */ } } private void sendTrackerStatesList(String id, Messenger replyTo) { if (id == null || replyTo == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task == null) { return; } try { ipc.sendGetTrackersStates(replyTo, makeTrackerStateParcelList(task)); } catch (RemoteException e) { /* The client is dead, ignore */ } } private void sendPeerStatesList(String id, Messenger replyTo) { if (id == null || replyTo == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task == null) { return; } try { ipc.sendGetPeersStates(replyTo, makePeerStateParcellList(task)); } catch (RemoteException e) { /* The client is dead, ignore */ } } private void sendPieces(String id, Messenger replyTo) { if (id == null || replyTo == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task == null) { return; } try { ipc.sendGetPieces(replyTo, task.pieces()); } catch (RemoteException e) { /* The client is dead, ignore */ } } private void sendMagnet(String id, Messenger replyTo) { if (id == null || replyTo == null) { return; } TorrentDownload task = torrentTasks.get(id); if (task == null) { return; } try { ipc.sendGetMagnet(replyTo, task.makeMagnet()); } catch (RemoteException e) { /* The client is dead, ignore */ } } /* * if id is null get global limit. */ private void sendSpeedLimit(String id, Messenger replyTo) { if (replyTo == null) { return; } if (id != null) { TorrentDownload task = torrentTasks.get(id); if (task == null) { return; } try { ipc.sendGetSpeedLimit(replyTo, task.getUploadSpeedLimit(), task.getDownloadSpeedLimit()); } catch (RemoteException e) { /* The client is dead, ignore */ } } else { try { ipc.sendGetSpeedLimit(replyTo, engineTask.getEngine().getUploadSpeedLimit(), engineTask.getEngine().getDownloadSpeedLimit()); } catch (RemoteException e) { /* The client is dead, ignore */ } } } private void startUpdateForegroundNotify() { if (updateForegroundNotifyHandler != null) { return; } updateForegroundNotifyHandler = new Handler(); Runnable r = new Runnable() { @Override public void run() { if (isAlreadyRunning) { boolean online = engineTask.getEngine().isListening(); if (isNetworkOnline != online) { isNetworkOnline = online; needsUpdateNotify.set(true); } if (needsUpdateNotify.get()) { try { needsUpdateNotify.set(false); if (foregroundNotify != null) { foregroundNotify .setContentText((isNetworkOnline ? getString(R.string.network_online) : getString(R.string.network_offline))); if (!torrentTasks.isEmpty()) { foregroundNotify.setStyle(makeDetailNotifyInboxStyle()); } else { foregroundNotify.setStyle(null); } /* Disallow killing the service process by system */ startForeground(SERVICE_STARTED_NOTIFICATION_ID, foregroundNotify.build()); } } catch (Exception e) { /* Ignore */ } } } updateForegroundNotifyHandler.postDelayed(this, SYNC_TIME); } }; updateForegroundNotifyHandler.postDelayed(r, SYNC_TIME); } private void makeForegroundNotify() { /* For starting main activity */ // TODO: 05.05.17 // Intent startupIntent = new Intent(getApplicationContext(), MainActivity.class); // startupIntent.setAction(Intent.ACTION_MAIN); // startupIntent.addCategory(Intent.CATEGORY_LAUNCHER); // startupIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // // PendingIntent startupPendingIntent = // PendingIntent.getActivity( // getApplicationContext(), // 0, // startupIntent, // PendingIntent.FLAG_UPDATE_CURRENT); // foregroundNotify = new NotificationCompat.Builder(getApplicationContext()) .setSmallIcon(R.drawable.ic_power_settings_new_white_24dp) // .setContentIntent(startupPendingIntent) .setContentTitle(getString(R.string.app_running_in_the_background)) .setTicker(getString(R.string.app_running_in_the_background)) .setContentText((isNetworkOnline ? getString(R.string.network_online) : getString(R.string.network_offline))) .setWhen(System.currentTimeMillis()); /* For shutdown activity and service */ Intent shutdownIntent = new Intent(getApplicationContext(), NotificationReceiver.class); shutdownIntent.setAction(NotificationReceiver.NOTIFY_ACTION_SHUTDOWN_APP); PendingIntent shutdownPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, shutdownIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Action shutdownAction = new NotificationCompat.Action.Builder( R.drawable.ic_power_settings_new_white_24dp, getString(R.string.shutdown), shutdownPendingIntent) .build(); foregroundNotify.addAction(shutdownAction); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { foregroundNotify.setCategory(Notification.CATEGORY_SERVICE); } /* Disallow killing the service process by system */ startForeground(SERVICE_STARTED_NOTIFICATION_ID, foregroundNotify.build()); } private NotificationCompat.InboxStyle makeDetailNotifyInboxStyle() { NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); String titleTemplate = getString(R.string.torrent_count_notify_template); int downloadingCount = 0; for (TorrentDownload task : torrentTasks.values()) { if (task == null) { continue; } String template; TorrentStateCode code = task.getStateCode(); if (code == TorrentStateCode.DOWNLOADING) { ++downloadingCount; template = getString(R.string.downloading_torrent_notify_template); inboxStyle.addLine(String.format(template, task.getProgress(), (task.getETA() == -1) ? Utils.INFINITY_SYMBOL : DateUtils.formatElapsedTime(task.getETA()), Formatter.formatFileSize(this, task.getDownloadSpeed()), task.getTorrent().getName())); } else if (code == TorrentStateCode.SEEDING) { template = getString(R.string.seeding_torrent_notify_template); inboxStyle.addLine(String.format(template, getString(R.string.torrent_status_seeding), Formatter.formatFileSize(this, task.getUploadSpeed()), task.getTorrent().getName())); } else { String stateString = ""; switch (task.getStateCode()) { case PAUSED: stateString = getString(R.string.torrent_status_paused); break; case STOPPED: stateString = getString(R.string.torrent_status_stopped); break; case CHECKING: stateString = getString(R.string.torrent_status_checking); break; } template = getString(R.string.other_torrent_notify_template); inboxStyle.addLine(String.format(template, stateString, task.getTorrent().getName())); } } inboxStyle.setBigContentTitle(String.format(titleTemplate, downloadingCount, torrentTasks.size())); inboxStyle.setSummaryText( (isNetworkOnline ? getString(R.string.network_online) : getString(R.string.network_offline))); return inboxStyle; } private void makeFinishNotify(Torrent torrent) { if (torrent == null || notifyManager == null || !pref.getBoolean(getString(R.string.pref_key_torrent_finish_notify), true)) { return; } NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()) .setSmallIcon(R.drawable.ic_done_white_24dp) .setColor(ContextCompat.getColor(getApplicationContext(), R.color.primary)) .setContentTitle(getString(R.string.torrent_finished_notify)) .setTicker(getString(R.string.torrent_finished_notify)).setContentText(torrent.getName()) .setWhen(System.currentTimeMillis()); if (pref.getBoolean(getString(R.string.pref_key_play_sound_notify), true)) { Uri sound = Uri.parse(pref.getString(getString(R.string.pref_key_notify_sound), RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString())); builder.setSound(sound); } if (pref.getBoolean(getString(R.string.pref_key_vibration_notify), true)) { /* TODO: Make the ability to customize vibration */ long[] vibration = new long[] { 1000 }; /* ms */ builder.setVibrate(vibration); } if (pref.getBoolean(getString(R.string.pref_key_led_indicator_notify), true)) { int color = pref.getInt(getString(R.string.pref_key_led_indicator_color_notify), ContextCompat.getColor(getApplicationContext(), R.color.primary)); builder.setLights(color, 1000, 1000); /* ms */ } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder.setCategory(Notification.CATEGORY_STATUS); } notifyManager.notify(torrent.getId().hashCode(), builder.build()); } private synchronized void makeTorrentsMoveNotify() { /* TODO: Make the ability to customize the sound, LED and vibration */ if (torrentsMoveTotal == null || torrentsMoveSuccess == null || torrentsMoveFailed == null || notifyManager == null) { return; } NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()); String resultTemplate = getString(R.string.torrents_moved_content); int successfully = torrentsMoveSuccess.size(); int failed = torrentsMoveFailed.size(); builder.setContentTitle(getString(R.string.torrents_moved_title)) .setTicker(getString(R.string.torrents_moved_title)) .setContentText(String.format(resultTemplate, successfully, failed)); builder.setSmallIcon(R.drawable.ic_folder_move_white_24dp).setAutoCancel(true) .setWhen(System.currentTimeMillis()) .setStyle(makeTorrentsMoveInboxStyle(torrentsMoveSuccess, torrentsMoveFailed)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder.setCategory(Notification.CATEGORY_STATUS); } notifyManager.notify(TORRENTS_MOVED_NOTIFICATION_ID, builder.build()); torrentsMoveTotal = null; torrentsMoveSuccess = null; torrentsMoveFailed = null; } private NotificationCompat.InboxStyle makeTorrentsMoveInboxStyle(List<String> torrentsMoveSuccess, List<String> torrentsMoveFailed) { NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); boolean successNotEmpty = !torrentsMoveSuccess.isEmpty(); if (successNotEmpty) { inboxStyle.addLine(getString(R.string.torrents_move_inbox_successfully)); for (String name : torrentsMoveSuccess) { inboxStyle.addLine(name); } } if (!torrentsMoveFailed.isEmpty()) { if (successNotEmpty) { inboxStyle.addLine("\n"); } inboxStyle.addLine(getString(R.string.torrents_move_inbox_failed)); for (String name : torrentsMoveFailed) { inboxStyle.addLine(name); } } return inboxStyle; } static class CallbackHandler extends Handler { WeakReference<TorrentTaskService> service; CallbackHandler(TorrentTaskService service) { this.service = new WeakReference<>(service); } @Override public void handleMessage(Message msg) { switch (msg.what) { case TorrentTaskServiceIPC.CLIENT_CONNECT: service.get().clientCallbacks.add(msg.replyTo); try { service.get().sendTorrentsStateOneShot(msg.replyTo); } catch (Exception e) { service.get().sendTorrentsList(); } break; case TorrentTaskServiceIPC.ADD_TORRENTS: { Bundle b = msg.getData(); b.setClassLoader(Torrent.class.getClassLoader()); ArrayList<Torrent> torrentsList = b.getParcelableArrayList(TorrentTaskServiceIPC.TAG_TORRENTS_LIST); ArrayList<Torrent> addedTorrentsList = new ArrayList<>(); ArrayList<TorrentStateParcel> states = new ArrayList<>(); ArrayList<Throwable> exceptions = new ArrayList<>(); if (torrentsList != null) { SettingsManager pref = new SettingsManager(service.get().getApplicationContext()); boolean deleteTorrentFile = pref .getBoolean(service.get().getString(R.string.pref_key_delete_torrent_file), false); for (Torrent torrent : torrentsList) { if (!new File(torrent.getTorrentFilePath()).exists()) { exceptions.add(new FileNotFoundException()); } else { try { if (service.get().repo.exists(torrent)) { service.get().repo.replace(torrent, torrent.getTorrentFilePath(), deleteTorrentFile); exceptions.add(new FileAlreadyExistsException()); } else { service.get().repo.add(torrent, torrent.getTorrentFilePath(), deleteTorrentFile); } torrent = service.get().repo.getTorrentByID(torrent.getId()); } catch (Throwable e) { exceptions.add(e); } if (torrent != null) { addedTorrentsList.add(torrent); states.add(new TorrentStateParcel(torrent.getId(), torrent.getName())); } } } } service.get().addTorrents(addedTorrentsList); service.get().sendOnAddTorrents(states, exceptions); break; } case TorrentTaskServiceIPC.CLIENT_DISCONNECT: service.get().clientCallbacks.remove(msg.replyTo); break; case TorrentTaskServiceIPC.UPDATE_STATES_ONESHOT: try { service.get().sendTorrentsStateOneShot(msg.replyTo); } catch (Exception e) { /* Ignore */ } break; case TorrentTaskServiceIPC.UPDATE_STATE: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); try { TorrentDownload task = service.get().torrentTasks.get(id); service.get().sendTorrentState(task); } catch (Exception e) { /* Ignore */ } break; } case TorrentTaskServiceIPC.PAUSE_RESUME_TORRENTS: { ArrayList<String> ids = msg.getData() .getStringArrayList(TorrentTaskServiceIPC.TAG_TORRENT_IDS_LIST); if (ids != null) { for (String id : ids) { TorrentDownload task = service.get().torrentTasks.get(id); try { if (task.isPaused()) { task.resume(); } else { task.pause(); } } catch (Exception e) { /* Ignore */ } } } break; } case TorrentTaskServiceIPC.DELETE_TORRENTS: case TorrentTaskServiceIPC.DELETE_TORRENTS_WITH_FILES: { ArrayList<String> ids = msg.getData() .getStringArrayList(TorrentTaskServiceIPC.TAG_TORRENT_IDS_LIST); service.get().deleteTorrents(ids, (msg.what == TorrentTaskServiceIPC.DELETE_TORRENTS_WITH_FILES)); break; } case TorrentTaskServiceIPC.FORCE_RECHECK_TORRENTS: { ArrayList<String> ids = msg.getData() .getStringArrayList(TorrentTaskServiceIPC.TAG_TORRENT_IDS_LIST); if (ids != null) { for (String id : ids) { service.get().torrentTasks.get(id).forceRecheck(); } } break; } case TorrentTaskServiceIPC.FORCE_ANNOUNCE_TORRENTS: { ArrayList<String> ids = msg.getData() .getStringArrayList(TorrentTaskServiceIPC.TAG_TORRENT_IDS_LIST); if (ids != null) { for (String id : ids) { service.get().torrentTasks.get(id).requestTrackerAnnounce(); } } break; } case TorrentTaskServiceIPC.GET_TORRENT_INFO: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); service.get().sendTorrentInfo(id, msg.replyTo); break; } case TorrentTaskServiceIPC.SET_TORRENT_NAME: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); String name = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_NAME); service.get().setTorrentName(id, name); break; } case TorrentTaskServiceIPC.SET_DOWNLOAD_PATH: { ArrayList<String> ids = msg.getData() .getStringArrayList(TorrentTaskServiceIPC.TAG_TORRENT_IDS_LIST); String path = msg.getData().getString(TorrentTaskServiceIPC.TAG_DOWNLOAD_PATH); service.get().setTorrentDownloadPath(ids, path); break; } case TorrentTaskServiceIPC.SET_SEQUENTIAL_DOWNLOAD: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); boolean sequential = msg.getData().getBoolean(TorrentTaskServiceIPC.TAG_SEQUENTIAL, false); service.get().setSequentialDownload(id, sequential); break; } case TorrentTaskServiceIPC.CHANGE_FILES_PRIORITY: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); ArrayList<Integer> priorities = msg.getData() .getIntegerArrayList(TorrentTaskServiceIPC.TAG_FILE_PRIORITIES); service.get().changeFilesPriority(id, priorities); break; } case TorrentTaskServiceIPC.GET_ACTIVE_AND_SEEDING_TIME: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); service.get().sendActiveAndSeedingTime(id, msg.replyTo); break; } case TorrentTaskServiceIPC.GET_TRACKERS_STATES: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); service.get().sendTrackerStatesList(id, msg.replyTo); break; } case TorrentTaskServiceIPC.REPLACE_TRACKERS: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); ArrayList<String> urls = msg.getData() .getStringArrayList(TorrentTaskServiceIPC.TAG_TRACKERS_URL_LIST); service.get().replaceTrackers(id, urls); break; } case TorrentTaskServiceIPC.ADD_TRACKERS: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); ArrayList<String> urls = msg.getData() .getStringArrayList(TorrentTaskServiceIPC.TAG_TRACKERS_URL_LIST); service.get().addTrackers(id, urls); break; } case TorrentTaskServiceIPC.GET_PEERS_STATES: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); service.get().sendPeerStatesList(id, msg.replyTo); break; } case TorrentTaskServiceIPC.GET_PIECES: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); service.get().sendPieces(id, msg.replyTo); break; } case TorrentTaskServiceIPC.GET_MAGNET: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); service.get().sendMagnet(id, msg.replyTo); break; } case TorrentTaskServiceIPC.SET_UPLOAD_SPEED_LIMIT: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); int limit = msg.arg1; service.get().setUploadSpeedLimit(id, limit); break; } case TorrentTaskServiceIPC.SET_DOWNLOAD_SPEED_LIMIT: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); int limit = msg.arg1; service.get().setDownloadSpeedLimit(id, limit); break; } case TorrentTaskServiceIPC.GET_SPEED_LIMIT: { String id = msg.getData().getString(TorrentTaskServiceIPC.TAG_TORRENT_ID); service.get().sendSpeedLimit(id, msg.replyTo); break; } default: super.handleMessage(msg); } } } }