diff --git a/app/src/main/java/com/gianlu/aria2app/Activities/MoreAboutDownloadActivity.java b/app/src/main/java/com/gianlu/aria2app/Activities/MoreAboutDownloadActivity.java index 69f60aa69..dd162a7c0 100644 --- a/app/src/main/java/com/gianlu/aria2app/Activities/MoreAboutDownloadActivity.java +++ b/app/src/main/java/com/gianlu/aria2app/Activities/MoreAboutDownloadActivity.java @@ -75,7 +75,7 @@ protected void onPreCreate(@Nullable Bundle savedInstanceState) { String title = getIntent().getStringExtra("title"); if (gid == null || theme == 0 || title == null) { super.onCreate(savedInstanceState); - Toaster.with(MoreAboutDownloadActivity.this).message(R.string.failedLoading).ex(new IllegalArgumentException("gid = " + gid + ", theme = " + theme + ", title = " + title)).show(); + Toaster.with(this).message(R.string.failedLoading).ex(new IllegalArgumentException("gid = " + gid + ", theme = " + theme + ", title = " + title)).show(); onBackPressed(); return; } diff --git a/app/src/main/java/com/gianlu/aria2app/LoadingActivity.java b/app/src/main/java/com/gianlu/aria2app/LoadingActivity.java index fc4b27845..04e69293e 100644 --- a/app/src/main/java/com/gianlu/aria2app/LoadingActivity.java +++ b/app/src/main/java/com/gianlu/aria2app/LoadingActivity.java @@ -17,6 +17,7 @@ import com.gianlu.aria2app.Main.MainActivity; import com.gianlu.aria2app.NetIO.AbstractClient; import com.gianlu.aria2app.NetIO.HttpClient; +import com.gianlu.aria2app.NetIO.NetInstanceHolder; import com.gianlu.aria2app.NetIO.OnConnect; import com.gianlu.aria2app.NetIO.WebSocketClient; import com.gianlu.aria2app.ProfilesManager.CustomProfilesAdapter; @@ -30,6 +31,7 @@ import org.json.JSONException; +import java.io.Closeable; import java.io.IOException; import java.util.List; import java.util.Objects; @@ -60,6 +62,7 @@ public class LoadingActivity extends ActivityWithDialog implements OnConnect, Dr private String shortcutAction; private Handler handler; private MultiProfile.UserProfile aria2AndroidProfile = null; + private Closeable ongoingTest; public static void startActivity(Context context) { context.startActivity(new Intent(context, LoadingActivity.class) @@ -107,7 +110,7 @@ protected void onCreate(Bundle savedInstanceState) { if (goTo != null) startActivity(goTo); }, 1000); - AbstractClient.invalidate(); + NetInstanceHolder.close(); manager = ProfilesManager.get(this); if (getIntent().getBooleanExtra("external", false)) { @@ -222,11 +225,10 @@ private void tryConnecting(@Nullable MultiProfile profile) { } else { manager.setCurrent(profile); MultiProfile.UserProfile single = profile.getProfile(this); - if (single.connectionMethod == MultiProfile.ConnectionMethod.WEBSOCKET) { - WebSocketClient.checkConnection(this, single, this); - } else { - HttpClient.checkConnection(this, single, this); - } + if (single.connectionMethod == MultiProfile.ConnectionMethod.WEBSOCKET) + ongoingTest = WebSocketClient.checkConnection(single, this); + else + ongoingTest = HttpClient.checkConnection(single, this); handler.postDelayed(() -> { cancel.setVisibility(View.VISIBLE); @@ -236,7 +238,13 @@ private void tryConnecting(@Nullable MultiProfile profile) { } private void cancelConnection() { - AbstractClient.invalidate(); + if (ongoingTest != null) { + try { + ongoingTest.close(); + } catch (IOException ignored) { + } + } + displayPicker(hasShareData()); seeError.setVisibility(View.GONE); } diff --git a/app/src/main/java/com/gianlu/aria2app/Main/MainActivity.java b/app/src/main/java/com/gianlu/aria2app/Main/MainActivity.java index bb68c1fb6..2ed96a9ee 100644 --- a/app/src/main/java/com/gianlu/aria2app/Main/MainActivity.java +++ b/app/src/main/java/com/gianlu/aria2app/Main/MainActivity.java @@ -47,6 +47,7 @@ import com.gianlu.aria2app.NetIO.AriaRequests; import com.gianlu.aria2app.NetIO.Downloader.FetchHelper; import com.gianlu.aria2app.NetIO.GitHubApi; +import com.gianlu.aria2app.NetIO.NetInstanceHolder; import com.gianlu.aria2app.NetIO.OnRefresh; import com.gianlu.aria2app.NetIO.Updater.PayloadProvider; import com.gianlu.aria2app.NetIO.Updater.Receiver; @@ -102,7 +103,7 @@ import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.ViewPager; -public class MainActivity extends UpdaterActivity implements FloatingActionsMenu.OnFloatingActionsMenuUpdateListener, TutorialManager.Listener, HideSecondSpace, DrawerManager.ProfilesDrawerListener, DownloadCardsAdapter.Listener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, MenuItem.OnActionExpandListener, AbstractClient.OnConnectivityChanged, OnRefresh, DrawerManager.MenuDrawerListener, FetchHelper.FetchDownloadCountListener { +public class MainActivity extends UpdaterActivity implements FloatingActionsMenu.OnFloatingActionsMenuUpdateListener, TutorialManager.Listener, HideSecondSpace, DrawerManager.ProfilesDrawerListener, DownloadCardsAdapter.Listener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, MenuItem.OnActionExpandListener, OnRefresh, DrawerManager.MenuDrawerListener, FetchHelper.FetchDownloadCountListener { private static final int REQUEST_READ_CODE = 12; private final static Wants MAIN_WANTS = Wants.downloadsAndStats(); private DrawerManager drawerManager; @@ -318,7 +319,7 @@ protected void onPostCreate() { helper = Aria2Helper.instantiate(this); } catch (ProfilesManager.NoCurrentProfileException | Aria2Helper.InitializingException ex) { Logging.log(ex); - AbstractClient.invalidate(); + NetInstanceHolder.close(); profilesManager.unsetLastProfile(); LoadingActivity.startActivity(this, ex); return; @@ -345,15 +346,15 @@ protected void onPostCreate() { fabMenu.setOnFloatingActionsMenuUpdateListener(this); FloatingActionButton fabSearch = findViewById(R.id.mainFab_search); - fabSearch.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, SearchActivity.class))); + fabSearch.setOnClickListener(v -> startActivity(new Intent(this, SearchActivity.class))); FloatingActionButton fabAddURI = findViewById(R.id.mainFab_addURI); - fabAddURI.setOnClickListener(view -> startActivity(new Intent(MainActivity.this, AddUriActivity.class))); + fabAddURI.setOnClickListener(view -> startActivity(new Intent(this, AddUriActivity.class))); FloatingActionButton fabAddTorrent = findViewById(R.id.mainFab_addTorrent); - fabAddTorrent.setOnClickListener(view -> startActivity(new Intent(MainActivity.this, AddTorrentActivity.class))); + fabAddTorrent.setOnClickListener(view -> startActivity(new Intent(this, AddTorrentActivity.class))); FloatingActionButton fabAddMetalink = findViewById(R.id.mainFab_addMetalink); - fabAddMetalink.setOnClickListener(view -> startActivity(new Intent(MainActivity.this, AddMetalinkActivity.class))); + fabAddMetalink.setOnClickListener(view -> startActivity(new Intent(this, AddMetalinkActivity.class))); FloatingActionButton fabBatchAdd = findViewById(R.id.mainFab_batchAdd); - fabBatchAdd.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, BatchAddActivity.class))); + fabBatchAdd.setOnClickListener(v -> startActivity(new Intent(this, BatchAddActivity.class))); recyclerViewLayout.startLoading(); @@ -469,7 +470,6 @@ public void onException(@NonNull Exception ex) { @Override protected void onDestroy() { - AbstractClient.removeConnectivityListener(this); if (adapter != null) adapter.activityDestroying(this); super.onDestroy(); } @@ -537,7 +537,7 @@ protected void onResume() { if (profilesManager != null) profilesManager.reloadCurrentProfile(); } catch (IOException | JSONException | ProfilesManager.NoCurrentProfileException ex) { Logging.log(ex); - AbstractClient.invalidate(); + NetInstanceHolder.close(); profilesManager.unsetLastProfile(); LoadingActivity.startActivity(this, ex); return; @@ -805,13 +805,6 @@ public void onItemCountUpdated(int count) { tutorialManager.tryShowingTutorials(this); } - @Override - protected void onStart() { - super.onStart(); - - AbstractClient.addConnectivityListener(this); - } - @Override public boolean onQueryTextSubmit(String query) { if (adapter != null) adapter.filterWithQuery(query); @@ -840,16 +833,6 @@ public boolean onMenuItemActionCollapse(MenuItem item) { return true; } - @Override - public void connectivityChanged(@NonNull final MultiProfile.UserProfile profile) { - if (drawerManager != null) drawerManager.setCurrentProfile(profile.getParent()); - - List profiles = profile.getParent().profiles; - if (!(profiles.size() >= 1 && profiles.get(0).connectivityCondition.type == MultiProfile.ConnectivityCondition.Type.ALWAYS)) { - Toaster.with(this).message(R.string.connectivityChanged).extra(profile.connectivityCondition).show(); - } - } - @Override public void refreshed() { adapter = new DownloadCardsAdapter(this, new ArrayList<>(), this); diff --git a/app/src/main/java/com/gianlu/aria2app/NetIO/AbstractClient.java b/app/src/main/java/com/gianlu/aria2app/NetIO/AbstractClient.java index ca3bc6f45..276ff7329 100644 --- a/app/src/main/java/com/gianlu/aria2app/NetIO/AbstractClient.java +++ b/app/src/main/java/com/gianlu/aria2app/NetIO/AbstractClient.java @@ -1,16 +1,9 @@ package com.gianlu.aria2app.NetIO; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Looper; import com.gianlu.aria2app.NetIO.Aria2.AriaException; -import com.gianlu.aria2app.NetIO.Downloader.FetchHelper; import com.gianlu.aria2app.ProfilesManager.MultiProfile; import com.gianlu.aria2app.ThisApplication; @@ -20,11 +13,7 @@ import java.io.Closeable; import java.io.IOException; -import java.lang.ref.WeakReference; import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import androidx.annotation.NonNull; @@ -32,93 +21,49 @@ import androidx.annotation.WorkerThread; import okhttp3.OkHttpClient; -public abstract class AbstractClient implements Closeable { - private static final List listeners = new ArrayList<>(); +public abstract class AbstractClient implements Closeable, ClientInterface { protected final OkHttpClient client; protected final Handler handler; - private final WifiManager wifiManager; - private final WeakReference context; - private final ConnectivityChangedReceiver connectivityChangedReceiver; private final AtomicLong requestIds = new AtomicLong(Long.MIN_VALUE); protected MultiProfile.UserProfile profile; protected volatile boolean closed = false; - AbstractClient(Context context, MultiProfile.UserProfile profile) throws GeneralSecurityException, IOException { - this.wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + AbstractClient(@NonNull MultiProfile.UserProfile profile) throws GeneralSecurityException, IOException { this.client = NetUtils.buildClient(profile); this.profile = profile; - this.context = new WeakReference<>(context); this.handler = new Handler(Looper.getMainLooper()); - this.connectivityChangedReceiver = new ConnectivityChangedReceiver(); - context.getApplicationContext().registerReceiver(connectivityChangedReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); ThisApplication.setCrashlyticsString("connectionMethod", profile.connectionMethod.name()); } - public static void addConnectivityListener(@NonNull OnConnectivityChanged listener) { - synchronized (listeners) { - listeners.add(listener); - } - } - - public static void removeConnectivityListener(@NonNull OnConnectivityChanged listener) { - synchronized (listeners) { - listeners.remove(listener); - } - } - - public static void invalidate() { - if (WebSocketClient.instance != null) - WebSocketClient.instance.close(); - - if (HttpClient.instance != null) - HttpClient.instance.close(); - } - - @Override - protected void finalize() throws Throwable { - removeReceiver(); - super.finalize(); - } - - private void removeReceiver() { - try { - if (context.get() != null && !closed) - context.get().getApplicationContext().unregisterReceiver(connectivityChangedReceiver); - } catch (IllegalArgumentException ignored) { - } - } - @Override @WorkerThread public final void close() { - removeReceiver(); - closed = true; - closeClient(); - listeners.clear(); + NetInstanceHolder.hasBeenClosed(this); } @WorkerThread protected abstract void closeClient(); + @Override public final void send(@NonNull final AriaRequestWithResult request, final OnResult listener) { try { JSONObject obj = request.build(this); send(request.id, obj, new OnJson() { @Override public void onResponse(@NonNull JSONObject response) throws JSONException { - final R result = request.processor.process(AbstractClient.this, response); + R result = request.processor.process(AbstractClient.this, response); handler.post(() -> listener.onResult(result)); } @Override - public void onException(@NonNull final Exception ex) { + public void onException(@NonNull Exception ex) { handler.post(() -> listener.onException(ex)); } }); - } catch (final JSONException ex) { + } catch (JSONException ex) { handler.post(() -> listener.onException(ex)); } } @@ -131,10 +76,11 @@ public final void sendSync(@NonNull AriaRequest request) throws Exception { @NonNull public final R sendSync(@NonNull AriaRequestWithResult request) throws Exception { JSONObject obj = request.build(this); - return request.processor.process(AbstractClient.this, sendSync(request.id, obj)); + return request.processor.process(this, sendSync(request.id, obj)); } - public final void send(@NonNull AriaRequest request, final OnSuccess listener) { + @Override + public final void send(@NonNull AriaRequest request, OnSuccess listener) { try { JSONObject obj = request.build(this); send(request.id, obj, new OnJson() { @@ -144,7 +90,7 @@ public void onResponse(@NonNull JSONObject response) { } @Override - public void onException(@NonNull final Exception ex) { + public void onException(@NonNull Exception ex) { handler.post(() -> listener.onException(ex)); } }); @@ -162,15 +108,15 @@ protected final void validateResponse(JSONObject resp) throws JSONException, Ari if (resp.has("error")) throw new AriaException(resp.getJSONObject("error")); } - public final void batch(BatchSandbox sandbox, final OnResult listener) { + public final void batch(BatchSandbox sandbox, OnResult listener) { batch(sandbox, new DoBatch() { @Override - public void onSandboxReturned(@NonNull final R result) { + public void onSandboxReturned(@NonNull R result) { handler.post(() -> listener.onResult(result)); } @Override - public void onException(@NonNull final Exception ex) { + public void onException(@NonNull Exception ex) { handler.post(() -> listener.onException(ex)); } }); @@ -178,17 +124,6 @@ public void onException(@NonNull final Exception ex) { protected abstract void batch(BatchSandbox sandbox, DoBatch listener); - @WorkerThread - protected abstract void connectivityChanged(@NonNull Context context, @NonNull MultiProfile.UserProfile profile) throws Exception; - - @WorkerThread - private void connectivityChangedInternal(@NonNull Context context, @NonNull MultiProfile.UserProfile profile) throws Exception { - if (this.profile.connectionMethod == profile.connectionMethod) - connectivityChanged(context, profile); - else - close(); - } - @NonNull private JSONArray baseRequestParams() { JSONArray array = new JSONArray(); @@ -268,11 +203,6 @@ public interface OnSuccess { void onException(@NonNull Exception ex); } - public interface OnConnectivityChanged { - @UiThread - void connectivityChanged(@NonNull MultiProfile.UserProfile profile); - } - public abstract static class Processor { @NonNull @@ -319,44 +249,4 @@ public static class InitializationException extends Exception { super(cause); } } - - private class ConnectivityChangedReceiver extends BroadcastReceiver { - - private void switchClients(final Context context, final MultiProfile.UserProfile updated) { - FetchHelper.invalidate(); - - synchronized (listeners) { - for (final OnConnectivityChanged listener : listeners) { - handler.post(() -> listener.connectivityChanged(updated)); - } - } - - new Thread() { - @Override - @WorkerThread - public void run() { - try { - connectivityChangedInternal(context, updated); - profile = updated; - } catch (Exception ex) { - ErrorHandler.get().notifyException(ex, true); - } - } - }.start(); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (Objects.equals(intent.getAction(), ConnectivityManager.CONNECTIVITY_ACTION)) { - boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - if (!noConnectivity) { - MultiProfile.UserProfile user = profile.getParent() - .getProfile(intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY), wifiManager); - - if (!Objects.equals(profile.connectivityCondition, user.connectivityCondition)) - switchClients(context, user); - } - } - } - } } diff --git a/app/src/main/java/com/gianlu/aria2app/NetIO/Aria2/Aria2Helper.java b/app/src/main/java/com/gianlu/aria2app/NetIO/Aria2/Aria2Helper.java index 3c8bbeb83..658708881 100644 --- a/app/src/main/java/com/gianlu/aria2app/NetIO/Aria2/Aria2Helper.java +++ b/app/src/main/java/com/gianlu/aria2app/NetIO/Aria2/Aria2Helper.java @@ -7,10 +7,9 @@ import com.gianlu.aria2app.Activities.AddDownload.AddDownloadBundle; import com.gianlu.aria2app.NetIO.AbstractClient; import com.gianlu.aria2app.NetIO.AriaRequests; -import com.gianlu.aria2app.NetIO.HttpClient; -import com.gianlu.aria2app.NetIO.WebSocketClient; +import com.gianlu.aria2app.NetIO.ClientInterface; +import com.gianlu.aria2app.NetIO.NetInstanceHolder; import com.gianlu.aria2app.PK; -import com.gianlu.aria2app.ProfilesManager.MultiProfile; import com.gianlu.aria2app.ProfilesManager.ProfilesManager; import com.gianlu.aria2app.R; import com.gianlu.commonutils.Preferences.Prefs; @@ -25,7 +24,7 @@ public class Aria2Helper { private static final AbstractClient.BatchSandbox VERSION_AND_SESSION_BATCH_SANDBOX = client -> new VersionAndSession(client.sendSync(AriaRequests.getVersion()), client.sendSync(AriaRequests.getSessionInfo())); - private final AbstractClient client; + private final ClientInterface client; private final AbstractClient.BatchSandbox DOWNLOADS_AND_GLOBAL_STATS_BATCH_SANDBOX = client -> { List all = new ArrayList<>(); all.addAll(client.sendSync(AriaRequests.tellActiveSmall())); @@ -34,17 +33,13 @@ public class Aria2Helper { return new DownloadsAndGlobalStats(all, Prefs.getBoolean(PK.A2_HIDE_METADATA), client.sendSync(AriaRequests.getGlobalStats())); }; - public Aria2Helper(@NonNull AbstractClient client) { + public Aria2Helper(@NonNull ClientInterface client) { this.client = client; } @NonNull - private static AbstractClient getClient(@NonNull Context context) throws AbstractClient.InitializationException, ProfilesManager.NoCurrentProfileException { - MultiProfile.UserProfile profile = ProfilesManager.get(context).getCurrentSpecific(); - if (profile.connectionMethod == MultiProfile.ConnectionMethod.WEBSOCKET) - return WebSocketClient.instantiate(context); - else - return HttpClient.instantiate(context); + private static ClientInterface getClient(@NonNull Context context) throws AbstractClient.InitializationException, ProfilesManager.NoCurrentProfileException { + return NetInstanceHolder.instantiate(ProfilesManager.get(context).getCurrentSpecific()); } @NonNull @@ -56,11 +51,11 @@ public static Aria2Helper instantiate(@NonNull Context context) throws Initializ } } - public final void request(final AbstractClient.AriaRequestWithResult request, final AbstractClient.OnResult listener) { + public final void request(AbstractClient.AriaRequestWithResult request, AbstractClient.OnResult listener) { client.send(request, listener); } - public final void request(final AbstractClient.AriaRequest request, final AbstractClient.OnSuccess listener) { + public final void request(AbstractClient.AriaRequest request, AbstractClient.OnSuccess listener) { client.send(request, listener); } @@ -89,11 +84,6 @@ public void addDownloads(@NonNull final Collection bundles, A }, listener); } - @NonNull - public AbstractClient getClient() { - return client; - } - public enum WhatAction { REMOVE, RESTART, RESUME, PAUSE, MOVE_UP, STOP, MOVE_DOWN } diff --git a/app/src/main/java/com/gianlu/aria2app/NetIO/ClientInterface.java b/app/src/main/java/com/gianlu/aria2app/NetIO/ClientInterface.java new file mode 100644 index 000000000..99bd4d757 --- /dev/null +++ b/app/src/main/java/com/gianlu/aria2app/NetIO/ClientInterface.java @@ -0,0 +1,13 @@ +package com.gianlu.aria2app.NetIO; + +import androidx.annotation.NonNull; + +public interface ClientInterface { + void close(); + + void send(@NonNull final AbstractClient.AriaRequestWithResult request, final AbstractClient.OnResult listener); + + void send(@NonNull AbstractClient.AriaRequest request, AbstractClient.OnSuccess listener); + + void batch(AbstractClient.BatchSandbox sandbox, AbstractClient.OnResult listener); +} diff --git a/app/src/main/java/com/gianlu/aria2app/NetIO/ConnectivityChangedReceiver.java b/app/src/main/java/com/gianlu/aria2app/NetIO/ConnectivityChangedReceiver.java new file mode 100644 index 000000000..5068828fb --- /dev/null +++ b/app/src/main/java/com/gianlu/aria2app/NetIO/ConnectivityChangedReceiver.java @@ -0,0 +1,28 @@ +package com.gianlu.aria2app.NetIO; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; + +import java.util.Objects; + +import androidx.annotation.NonNull; + +public class ConnectivityChangedReceiver extends BroadcastReceiver { + private final WifiManager wifiManager; + + public ConnectivityChangedReceiver(@NonNull Context context) { + this.wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Objects.equals(intent.getAction(), ConnectivityManager.CONNECTIVITY_ACTION)) { + boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); + if (!noConnectivity) + NetInstanceHolder.handleConnectivityChange(context, intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY), wifiManager); + } + } +} diff --git a/app/src/main/java/com/gianlu/aria2app/NetIO/HttpClient.java b/app/src/main/java/com/gianlu/aria2app/NetIO/HttpClient.java index cca73638c..5e588d56a 100644 --- a/app/src/main/java/com/gianlu/aria2app/NetIO/HttpClient.java +++ b/app/src/main/java/com/gianlu/aria2app/NetIO/HttpClient.java @@ -1,15 +1,13 @@ package com.gianlu.aria2app.NetIO; -import android.content.Context; - import com.gianlu.aria2app.NetIO.Aria2.AriaException; import com.gianlu.aria2app.ProfilesManager.MultiProfile; -import com.gianlu.aria2app.ProfilesManager.ProfilesManager; import com.gianlu.commonutils.Logging; import org.json.JSONException; import org.json.JSONObject; +import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; @@ -21,29 +19,27 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import okhttp3.Response; import okhttp3.ResponseBody; public class HttpClient extends AbstractClient { - static HttpClient instance; private final ExecutorService executorService; private final URI url; - private HttpClient(@NonNull Context context) throws GeneralSecurityException, IOException, NetUtils.InvalidUrlException, ProfilesManager.NoCurrentProfileException { - this(context, ProfilesManager.get(context).getCurrentSpecific()); - } - - private HttpClient(@NonNull Context context, @NonNull MultiProfile.UserProfile profile) throws GeneralSecurityException, IOException, NetUtils.InvalidUrlException { - super(context, profile); + @UiThread + private HttpClient(@NonNull MultiProfile.UserProfile profile) throws GeneralSecurityException, IOException, NetUtils.InvalidUrlException { + super(profile); ErrorHandler.get().unlock(); this.executorService = Executors.newCachedThreadPool(); this.url = NetUtils.createHttpURL(profile); } - private HttpClient(@NonNull Context context, @NonNull MultiProfile.UserProfile profile, @Nullable OnConnect connectionListener) throws GeneralSecurityException, IOException, NetUtils.InvalidUrlException { - this(context, profile); + @UiThread + private HttpClient(@NonNull MultiProfile.UserProfile profile, @Nullable OnConnect connectionListener) throws GeneralSecurityException, IOException, NetUtils.InvalidUrlException { + this(profile); executorService.submit(() -> { try (Socket socket = new Socket()) { @@ -55,45 +51,40 @@ private HttpClient(@NonNull Context context, @NonNull MultiProfile.UserProfile p connectionListener.onPingTested(HttpClient.this, System.currentTimeMillis() - initializedAt); }); } - } catch (final IOException ex) { + } catch (IOException ex) { if (connectionListener != null) { handler.post(() -> connectionListener.onFailedConnecting(profile, ex)); } else { Logging.log(ex); } - - close(); } + + close(); }); } @NonNull - public static HttpClient instantiate(@NonNull Context context) throws InitializationException { - if (instance == null) { - try { - instance = new HttpClient(context); - } catch (GeneralSecurityException | IOException | NetUtils.InvalidUrlException | ProfilesManager.NoCurrentProfileException ex) { - throw new InitializationException(ex); - } + static HttpClient instantiate(@NonNull MultiProfile.UserProfile profile) throws InitializationException { + try { + return new HttpClient(profile); + } catch (GeneralSecurityException | IOException | NetUtils.InvalidUrlException ex) { + throw new InitializationException(ex); } - - return instance; } - public static void checkConnection(@NonNull Context context, @NonNull MultiProfile.UserProfile profile, @NonNull OnConnect listener) { + @UiThread + public static Closeable checkConnection(@NonNull MultiProfile.UserProfile profile, @NonNull OnConnect listener) { try { - new HttpClient(context, profile, listener); + return new HttpClient(profile, listener); } catch (GeneralSecurityException | IOException | NetUtils.InvalidUrlException ex) { listener.onFailedConnecting(profile, ex); + return null; } } @Override protected void closeClient() { executorService.shutdownNow(); - - if (instance == this) - instance = null; } @Override @@ -131,11 +122,6 @@ private void executeRunnable(Runnable runnable) { executorService.execute(runnable); } - @Override - public void connectivityChanged(@NonNull Context context, @NonNull MultiProfile.UserProfile profile) throws Exception { - if (this == instance) instance = new HttpClient(context, profile); - } - private class SandboxRunnable implements Runnable { private final BatchSandbox sandbox; private final DoBatch listener; diff --git a/app/src/main/java/com/gianlu/aria2app/NetIO/NetInstanceHolder.java b/app/src/main/java/com/gianlu/aria2app/NetIO/NetInstanceHolder.java new file mode 100644 index 000000000..a6f1c6e80 --- /dev/null +++ b/app/src/main/java/com/gianlu/aria2app/NetIO/NetInstanceHolder.java @@ -0,0 +1,97 @@ +package com.gianlu.aria2app.NetIO; + +import android.content.Context; +import android.net.wifi.WifiManager; + +import com.gianlu.aria2app.LoadingActivity; +import com.gianlu.aria2app.NetIO.Downloader.FetchHelper; +import com.gianlu.aria2app.ProfilesManager.MultiProfile; +import com.gianlu.aria2app.ProfilesManager.ProfilesManager; +import com.gianlu.commonutils.Logging; + +import java.util.Objects; + +import androidx.annotation.NonNull; + +public final class NetInstanceHolder { + private static NetInstanceHolder instance = new NetInstanceHolder(); + private final Reference reference = new Reference(); + private AbstractClient current; + + private NetInstanceHolder() { + } + + static void handleConnectivityChange(Context context, int networkType, @NonNull WifiManager wifiManager) { + if (instance.current == null) return; + + MultiProfile.UserProfile user = instance.current.profile.getParent().getProfile(networkType, wifiManager); + if (!Objects.equals(instance.current.profile.connectivityCondition, user.connectivityCondition)) { + FetchHelper.invalidate(); + + try { + close(); + instantiate(user); + } catch (AbstractClient.InitializationException ex) { + Logging.log(ex); + NetInstanceHolder.close(); + ProfilesManager.get(context).unsetLastProfile(); + LoadingActivity.startActivity(context, ex); + } + } + } + + @NonNull + public static Reference instantiate(@NonNull MultiProfile.UserProfile profile) throws AbstractClient.InitializationException { + instance.handleInstantiate(profile); + return instance.reference; + } + + public static void close() { + if (instance.current != null) + instance.current.close(); + } + + static void hasBeenClosed(@NonNull AbstractClient client) { + if (instance.current == client) + instance.current = null; + } + + private void handleInstantiate(@NonNull MultiProfile.UserProfile profile) throws AbstractClient.InitializationException { + if (profile.connectionMethod == MultiProfile.ConnectionMethod.WEBSOCKET) { + if (!(current instanceof WebSocketClient)) { + if (current != null) current.close(); + current = WebSocketClient.instantiate(profile); + } + } else if (profile.connectionMethod == MultiProfile.ConnectionMethod.HTTP) { + if (!(current instanceof HttpClient)) { + if (current != null) current.close(); + current = HttpClient.instantiate(profile); + } + } else { + throw new IllegalStateException("Unknown connection method: " + profile.connectionMethod); + } + } + + public class Reference implements ClientInterface { + + @Override + public void close() { + if (current != null) current.close(); + } + + @Override + public void send(@NonNull AbstractClient.AriaRequestWithResult request, AbstractClient.OnResult listener) { + if (current != null) current.send(request, listener); + } + + @Override + public void send(@NonNull AbstractClient.AriaRequest request, AbstractClient.OnSuccess listener) { + if (current != null) current.send(request, listener); + } + + @Override + public void batch(AbstractClient.BatchSandbox sandbox, AbstractClient.OnResult listener) { + if (current != null) current.batch(sandbox, listener); + } + } +} diff --git a/app/src/main/java/com/gianlu/aria2app/NetIO/WebSocketClient.java b/app/src/main/java/com/gianlu/aria2app/NetIO/WebSocketClient.java index 80dc9dda6..c9eb1ca9a 100644 --- a/app/src/main/java/com/gianlu/aria2app/NetIO/WebSocketClient.java +++ b/app/src/main/java/com/gianlu/aria2app/NetIO/WebSocketClient.java @@ -1,14 +1,12 @@ package com.gianlu.aria2app.NetIO; -import android.content.Context; - import com.gianlu.aria2app.NetIO.Aria2.AriaException; import com.gianlu.aria2app.ProfilesManager.MultiProfile; -import com.gianlu.aria2app.ProfilesManager.ProfilesManager; import org.json.JSONException; import org.json.JSONObject; +import java.io.Closeable; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Map; @@ -19,52 +17,48 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import okhttp3.Response; import okhttp3.WebSocket; import okhttp3.WebSocketListener; public class WebSocketClient extends AbstractClient { - static WebSocketClient instance; private final Map requests = new ConcurrentHashMap<>(); private final WebSocket webSocket; private final long initializedAt; private final ExecutorService executorService = Executors.newCachedThreadPool(); private OnConnect connectionListener = null; - private WebSocketClient(@NonNull Context context, @NonNull MultiProfile.UserProfile profile) throws GeneralSecurityException, NetUtils.InvalidUrlException, IOException { - super(context, profile); + @UiThread + private WebSocketClient(@NonNull MultiProfile.UserProfile profile) throws GeneralSecurityException, NetUtils.InvalidUrlException, IOException { + super(profile); webSocket = client.newWebSocket(NetUtils.createWebsocketRequest(profile), new Listener()); initializedAt = System.currentTimeMillis(); } - private WebSocketClient(@NonNull Context context) throws GeneralSecurityException, ProfilesManager.NoCurrentProfileException, NetUtils.InvalidUrlException, IOException { - this(context, ProfilesManager.get(context).getCurrentSpecific()); - } - - private WebSocketClient(@NonNull Context context, @NonNull MultiProfile.UserProfile profile, @Nullable OnConnect listener) throws GeneralSecurityException, NetUtils.InvalidUrlException, IOException { - this(context, profile); + @UiThread + private WebSocketClient(@NonNull MultiProfile.UserProfile profile, @Nullable OnConnect listener) throws GeneralSecurityException, NetUtils.InvalidUrlException, IOException { + this(profile); connectionListener = listener; } @NonNull - public static WebSocketClient instantiate(@NonNull Context context) throws InitializationException { - if (instance == null || instance.closed) { - try { - instance = new WebSocketClient(context); - } catch (NetUtils.InvalidUrlException | ProfilesManager.NoCurrentProfileException | GeneralSecurityException | IOException ex) { - throw new InitializationException(ex); - } + static WebSocketClient instantiate(@NonNull MultiProfile.UserProfile profile) throws InitializationException { + try { + return new WebSocketClient(profile); + } catch (NetUtils.InvalidUrlException | GeneralSecurityException | IOException ex) { + throw new InitializationException(ex); } - - return instance; } - public static void checkConnection(@NonNull Context context, @NonNull MultiProfile.UserProfile profile, @NonNull OnConnect listener) { + @UiThread + public static Closeable checkConnection(@NonNull MultiProfile.UserProfile profile, @NonNull OnConnect listener) { try { - new WebSocketClient(context, profile, listener); + return new WebSocketClient(profile, listener); } catch (NetUtils.InvalidUrlException | GeneralSecurityException | IOException ex) { listener.onFailedConnecting(profile, ex); + return null; } } @@ -72,16 +66,17 @@ public static void checkConnection(@NonNull Context context, @NonNull MultiProfi protected void closeClient() { connectionListener = null; if (webSocket != null) webSocket.close(1000, null); - if (requests != null) requests.clear(); - if (instance == this) - instance = null; + for (OnJson listener : requests.values()) + listener.onException(new IOException("Client has been closed.")); + + requests.clear(); } @Override public void send(long id, @NonNull JSONObject request, @NonNull OnJson listener) { if (closed) { - listener.onException(new IllegalStateException("Client is closed!")); + listener.onException(new IllegalStateException("Client is closed: " + this)); return; } @@ -132,11 +127,6 @@ protected void batch(BatchSandbox sandbox, DoBatch listener) { executorService.execute(new SandboxRunnable<>(sandbox, listener)); } - @Override - public void connectivityChanged(@NonNull Context context, @NonNull MultiProfile.UserProfile profile) throws Exception { - if (this == instance) instance = new WebSocketClient(context, profile, null); - } - @WorkerThread private class Listener extends WebSocketListener { @Override @@ -173,6 +163,7 @@ public void onOpen(WebSocket webSocket, Response response) { } connectionListener = null; + close(); } }); } diff --git a/app/src/main/java/com/gianlu/aria2app/ProfilesManager/Testers/NetTester.java b/app/src/main/java/com/gianlu/aria2app/ProfilesManager/Testers/NetTester.java index d828c9354..a137303b5 100644 --- a/app/src/main/java/com/gianlu/aria2app/ProfilesManager/Testers/NetTester.java +++ b/app/src/main/java/com/gianlu/aria2app/ProfilesManager/Testers/NetTester.java @@ -60,10 +60,10 @@ public void onFailedConnecting(@NonNull MultiProfile.UserProfile profile, @NonNu switch (profile.connectionMethod) { case HTTP: - HttpClient.checkConnection(context, profile, listener); + HttpClient.checkConnection(profile, listener); break; case WEBSOCKET: - WebSocketClient.checkConnection(context, profile, listener); + WebSocketClient.checkConnection(profile, listener); break; default: return null; diff --git a/app/src/main/java/com/gianlu/aria2app/ThisApplication.java b/app/src/main/java/com/gianlu/aria2app/ThisApplication.java index a11b4ca07..a8a317a72 100644 --- a/app/src/main/java/com/gianlu/aria2app/ThisApplication.java +++ b/app/src/main/java/com/gianlu/aria2app/ThisApplication.java @@ -1,9 +1,12 @@ package com.gianlu.aria2app; +import android.content.IntentFilter; +import android.net.ConnectivityManager; import android.preference.PreferenceManager; -import com.gianlu.aria2app.NetIO.AbstractClient; +import com.gianlu.aria2app.NetIO.ConnectivityChangedReceiver; import com.gianlu.aria2app.NetIO.ErrorHandler; +import com.gianlu.aria2app.NetIO.NetInstanceHolder; import com.gianlu.aria2app.NetIO.Search.SearchApi; import com.gianlu.aria2app.ProfilesManager.ProfilesManager; import com.gianlu.aria2app.Services.NotificationService; @@ -25,6 +28,7 @@ public final class ThisApplication extends AnalyticsApplication implements Error public static final boolean DEBUG_UPDATER = false; public static final boolean DEBUG_NOTIFICATION = false; private final Set checkedVersionFor = new HashSet<>(); + private ConnectivityChangedReceiver connectivityChangedReceiver; public boolean shouldCheckVersion() { try { @@ -79,6 +83,15 @@ public void onCreate() { NotificationService.stop(ThisApplication.this); } }); + + connectivityChangedReceiver = new ConnectivityChangedReceiver(this); + getApplicationContext().registerReceiver(connectivityChangedReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + @Override + public void onTerminate() { + getApplicationContext().unregisterReceiver(connectivityChangedReceiver); + super.onTerminate(); } @SuppressWarnings("deprecation") @@ -101,7 +114,7 @@ private void deprecatedBackwardCompatibility() { @Override public void onFatal(@NonNull Throwable ex) { - AbstractClient.invalidate(); + NetInstanceHolder.close(); Toaster.with(this).message(R.string.fatalExceptionMessage).ex(ex).show(); LoadingActivity.startActivity(this, ex); @@ -110,7 +123,7 @@ public void onFatal(@NonNull Throwable ex) { @Override public void onSubsequentExceptions() { - AbstractClient.invalidate(); + NetInstanceHolder.close(); LoadingActivity.startActivity(this, null); } diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 974cfb915..2e68039ae 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -207,7 +207,6 @@ 更改选项失败! 查看错误 联系我 - 连接已改变,切换配置文件。 加载失败: %s 这个搜索引擎由我(作者本人)提供支持。它结合了各种来源的结果。 没有服务器 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 250a5420d..af1e5b1f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -214,7 +214,6 @@ Failed changing options! See error Contact me - Connectivity changed, switching profile. Failed loading: %s This search engine is powered by me (the developer). It combines results from various sources. No servers