Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>master
@@ -23,7 +23,6 @@ import com.wireguard.android.model.TunnelManager; | |||||
import com.wireguard.android.util.AsyncWorker; | import com.wireguard.android.util.AsyncWorker; | ||||
import com.wireguard.android.util.RootShell; | import com.wireguard.android.util.RootShell; | ||||
import com.wireguard.android.util.ToolsInstaller; | import com.wireguard.android.util.ToolsInstaller; | ||||
import com.wireguard.android.util.Topic; | |||||
import java.io.File; | import java.io.File; | ||||
import java.util.concurrent.Executor; | import java.util.concurrent.Executor; | ||||
@@ -68,8 +67,6 @@ public class Application extends android.app.Application { | |||||
ToolsInstaller getToolsInstaller(); | ToolsInstaller getToolsInstaller(); | ||||
TunnelManager getTunnelManager(); | TunnelManager getTunnelManager(); | ||||
Topic getThemeChangeTopic(); | |||||
} | } | ||||
@Qualifier | @Qualifier | ||||
@@ -119,13 +116,6 @@ public class Application extends android.app.Application { | |||||
return new FileConfigStore(context); | return new FileConfigStore(context); | ||||
} | } | ||||
@ApplicationScope | |||||
@Provides | |||||
public static Topic getThemeChangeTopic() { | |||||
return new Topic(); | |||||
} | |||||
@ApplicationScope | @ApplicationScope | ||||
@Provides | @Provides | ||||
public static Executor getExecutor() { | public static Executor getExecutor() { | ||||
@@ -11,24 +11,19 @@ import android.databinding.CallbackRegistry; | |||||
import android.databinding.CallbackRegistry.NotifierCallback; | import android.databinding.CallbackRegistry.NotifierCallback; | ||||
import android.os.Bundle; | import android.os.Bundle; | ||||
import android.support.v7.app.AppCompatActivity; | import android.support.v7.app.AppCompatActivity; | ||||
import android.util.Log; | |||||
import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||
import com.wireguard.android.backend.GoBackend; | import com.wireguard.android.backend.GoBackend; | ||||
import com.wireguard.android.model.Tunnel; | import com.wireguard.android.model.Tunnel; | ||||
import com.wireguard.android.model.TunnelManager; | import com.wireguard.android.model.TunnelManager; | ||||
import com.wireguard.android.util.Topic; | |||||
import java.lang.reflect.Field; | |||||
import java.util.Objects; | import java.util.Objects; | ||||
/** | /** | ||||
* Base class for activities that need to remember the currently-selected tunnel. | * Base class for activities that need to remember the currently-selected tunnel. | ||||
*/ | */ | ||||
public abstract class BaseActivity extends AppCompatActivity implements Topic.Subscriber { | |||||
private static final String TAG = "WireGuard/" + BaseActivity.class.getSimpleName(); | |||||
public abstract class BaseActivity extends ThemeChangeAwareActivity { | |||||
private static final String KEY_SELECTED_TUNNEL = "selected_tunnel"; | private static final String KEY_SELECTED_TUNNEL = "selected_tunnel"; | ||||
private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry(); | private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry(); | ||||
@@ -45,8 +40,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Su | |||||
@Override | @Override | ||||
protected void onCreate(final Bundle savedInstanceState) { | protected void onCreate(final Bundle savedInstanceState) { | ||||
subscribeTopics(); | |||||
// Restore the saved tunnel if there is one; otherwise grab it from the arguments. | // Restore the saved tunnel if there is one; otherwise grab it from the arguments. | ||||
String savedTunnelName = null; | String savedTunnelName = null; | ||||
if (savedInstanceState != null) | if (savedInstanceState != null) | ||||
@@ -69,12 +62,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Su | |||||
} | } | ||||
} | } | ||||
@Override | |||||
protected void onDestroy() { | |||||
unsubscribeTopics(); | |||||
super.onDestroy(); | |||||
} | |||||
@Override | @Override | ||||
protected void onSaveInstanceState(final Bundle outState) { | protected void onSaveInstanceState(final Bundle outState) { | ||||
if (selectedTunnel != null) | if (selectedTunnel != null) | ||||
@@ -118,35 +105,4 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Su | |||||
super(new SelectionChangeNotifier()); | super(new SelectionChangeNotifier()); | ||||
} | } | ||||
} | } | ||||
@Override | |||||
public void onTopicPublished(final Topic topic) { | |||||
if (topic == Application.getComponent().getThemeChangeTopic()) { | |||||
try { | |||||
Field f; | |||||
Object o = getResources(); | |||||
try { | |||||
f = o.getClass().getDeclaredField("mResourcesImpl"); | |||||
f.setAccessible(true); | |||||
o = f.get(o); | |||||
} catch (final Exception ignored) { } | |||||
f = o.getClass().getDeclaredField("mDrawableCache"); | |||||
f.setAccessible(true); | |||||
o = f.get(o); | |||||
try { | |||||
o.getClass().getMethod("onConfigurationChange", int.class).invoke(o, -1); | |||||
} catch (final Exception ignored) { | |||||
o.getClass().getMethod("clear").invoke(o); | |||||
} | |||||
} catch (final Exception e) { | |||||
Log.e(TAG, "Failed to flush drawable cache", e); | |||||
} | |||||
recreate(); | |||||
} | |||||
} | |||||
@Override | |||||
public Topic[] getSubscription() { | |||||
return new Topic[] { Application.getComponent().getThemeChangeTopic() }; | |||||
} | |||||
} | } |
@@ -21,7 +21,6 @@ import android.view.MenuItem; | |||||
import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||
import com.wireguard.android.R; | import com.wireguard.android.R; | ||||
import com.wireguard.android.backend.WgQuickBackend; | import com.wireguard.android.backend.WgQuickBackend; | ||||
import com.wireguard.android.util.Topic; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
@@ -33,7 +32,7 @@ import java.util.Map; | |||||
* Interface for changing application-global persistent settings. | * Interface for changing application-global persistent settings. | ||||
*/ | */ | ||||
public class SettingsActivity extends AppCompatActivity implements Topic.Subscriber { | |||||
public class SettingsActivity extends ThemeChangeAwareActivity { | |||||
private final Map<Integer, PermissionRequestCallback> permissionRequestCallbacks = new HashMap<>(); | private final Map<Integer, PermissionRequestCallback> permissionRequestCallbacks = new HashMap<>(); | ||||
private int permissionRequestCounter; | private int permissionRequestCounter; | ||||
@@ -59,7 +58,6 @@ public class SettingsActivity extends AppCompatActivity implements Topic.Subscri | |||||
@Override | @Override | ||||
protected void onCreate(final Bundle savedInstanceState) { | protected void onCreate(final Bundle savedInstanceState) { | ||||
super.onCreate(savedInstanceState); | super.onCreate(savedInstanceState); | ||||
subscribeTopics(); | |||||
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) { | if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) { | ||||
getSupportFragmentManager().beginTransaction() | getSupportFragmentManager().beginTransaction() | ||||
.add(android.R.id.content, new SettingsFragment()) | .add(android.R.id.content, new SettingsFragment()) | ||||
@@ -67,12 +65,6 @@ public class SettingsActivity extends AppCompatActivity implements Topic.Subscri | |||||
} | } | ||||
} | } | ||||
@Override | |||||
protected void onDestroy() { | |||||
unsubscribeTopics(); | |||||
super.onDestroy(); | |||||
} | |||||
@Override | @Override | ||||
public boolean onOptionsItemSelected(final MenuItem item) { | public boolean onOptionsItemSelected(final MenuItem item) { | ||||
switch (item.getItemId()) { | switch (item.getItemId()) { | ||||
@@ -99,18 +91,7 @@ public class SettingsActivity extends AppCompatActivity implements Topic.Subscri | |||||
void done(String[] permissions, int[] grantResults); | void done(String[] permissions, int[] grantResults); | ||||
} | } | ||||
@Override | |||||
public void onTopicPublished(final Topic topic) { | |||||
if (topic == Application.getComponent().getThemeChangeTopic()) | |||||
recreate(); | |||||
} | |||||
@Override | |||||
public Topic[] getSubscription() { | |||||
return new Topic[] { Application.getComponent().getThemeChangeTopic() }; | |||||
} | |||||
public static class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { | |||||
public static class SettingsFragment extends PreferenceFragmentCompat { | |||||
@Override | @Override | ||||
public void onCreatePreferences(final Bundle savedInstanceState, final String key) { | public void onCreatePreferences(final Bundle savedInstanceState, final String key) { | ||||
addPreferencesFromResource(R.xml.preferences); | addPreferencesFromResource(R.xml.preferences); | ||||
@@ -121,26 +102,5 @@ public class SettingsActivity extends AppCompatActivity implements Topic.Subscri | |||||
getPreferenceScreen().removePreference(pref); | getPreferenceScreen().removePreference(pref); | ||||
} | } | ||||
} | } | ||||
@Override | |||||
public void onResume() { | |||||
super.onResume(); | |||||
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); | |||||
} | |||||
@Override | |||||
public void onPause() { | |||||
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); | |||||
super.onPause(); | |||||
} | |||||
@Override | |||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { | |||||
if ("dark_theme".equals(key)) { | |||||
AppCompatDelegate.setDefaultNightMode( | |||||
sharedPreferences.getBoolean(key, false) ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); | |||||
Application.getComponent().getThemeChangeTopic().publish(false); | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -0,0 +1,76 @@ | |||||
/* | |||||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. | |||||
* SPDX-License-Identifier: GPL-2.0-or-later | |||||
*/ | |||||
package com.wireguard.android.activity; | |||||
import android.content.SharedPreferences; | |||||
import android.content.res.Resources; | |||||
import android.os.Bundle; | |||||
import android.preference.PreferenceManager; | |||||
import android.support.v7.app.AppCompatActivity; | |||||
import android.support.v7.app.AppCompatDelegate; | |||||
import android.util.Log; | |||||
import java.lang.reflect.Field; | |||||
public abstract class ThemeChangeAwareActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { | |||||
private static final String TAG = "WireGuard/" + ThemeChangeAwareActivity.class.getSimpleName(); | |||||
private static Resources lastResources; | |||||
private static boolean lastDarkMode; | |||||
private static synchronized void invalidateDrawableCache(final Resources resources, final boolean darkMode) { | |||||
if (resources == lastResources && darkMode == lastDarkMode) | |||||
return; | |||||
try { | |||||
Field f; | |||||
Object o = resources; | |||||
try { | |||||
f = o.getClass().getDeclaredField("mResourcesImpl"); | |||||
f.setAccessible(true); | |||||
o = f.get(o); | |||||
} catch (final Exception ignored) { } | |||||
f = o.getClass().getDeclaredField("mDrawableCache"); | |||||
f.setAccessible(true); | |||||
o = f.get(o); | |||||
try { | |||||
o.getClass().getMethod("onConfigurationChange", int.class).invoke(o, -1); | |||||
} catch (final Exception ignored) { | |||||
o.getClass().getMethod("clear").invoke(o); | |||||
} | |||||
} catch (final Exception e) { | |||||
Log.e(TAG, "Failed to flush drawable cache", e); | |||||
} | |||||
lastResources = resources; | |||||
lastDarkMode = darkMode; | |||||
} | |||||
@Override | |||||
protected void onCreate(final Bundle savedInstanceState) { | |||||
super.onCreate(savedInstanceState); | |||||
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); | |||||
} | |||||
@Override | |||||
protected void onDestroy() { | |||||
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this); | |||||
super.onDestroy(); | |||||
} | |||||
@Override | |||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { | |||||
if ("dark_theme".equals(key)) { | |||||
final boolean darkMode = sharedPreferences.getBoolean(key, false); | |||||
AppCompatDelegate.setDefaultNightMode( | |||||
sharedPreferences.getBoolean(key, false) ? | |||||
AppCompatDelegate.MODE_NIGHT_YES : | |||||
AppCompatDelegate.MODE_NIGHT_NO); | |||||
invalidateDrawableCache(getResources(), darkMode); | |||||
recreate(); | |||||
} | |||||
} | |||||
} |
@@ -1,109 +0,0 @@ | |||||
/* | |||||
* Copyright © 2017 John Wu <topjohnwu@gmail.com> | |||||
* SPDX-License-Identifier: GPL-2.0-or-later | |||||
*/ | |||||
package com.wireguard.android.util; | |||||
import java.lang.ref.WeakReference; | |||||
import java.util.Iterator; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import java.lang.ref.WeakReference; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
public class Topic { | |||||
private static final int NON_INIT = 0; | |||||
private static final int PENDING = 1; | |||||
private static final int PUBLISHED = 2; | |||||
private int state = NON_INIT; | |||||
private List<WeakReference<Subscriber>> subscribers; | |||||
private Object[] results; | |||||
public Topic() { | |||||
subscribers = new SyncArrayList<>(); | |||||
} | |||||
public synchronized void subscribe(final Subscriber sub) { | |||||
subscribers.add(new WeakReference<>(sub)); | |||||
} | |||||
public synchronized void unsubscribe() { | |||||
subscribers = new SyncArrayList<>(); | |||||
} | |||||
public synchronized void unsubscribe(final Subscriber sub) { | |||||
List<WeakReference<Subscriber>> subs = subscribers; | |||||
subscribers = new ArrayList<>(); | |||||
for (final WeakReference<Subscriber> subscriber : subs) { | |||||
if (subscriber.get() != null && subscriber.get() != sub) | |||||
subscribers.add(subscriber); | |||||
} | |||||
} | |||||
public void reset() { | |||||
state = NON_INIT; | |||||
results = null; | |||||
} | |||||
public boolean isPublished() { | |||||
return state == PUBLISHED; | |||||
} | |||||
public void publish() { | |||||
publish(true); | |||||
} | |||||
public void publish(boolean record, Object... results) { | |||||
if (record) | |||||
state = PUBLISHED; | |||||
this.results = results; | |||||
// Snapshot | |||||
List<WeakReference<Subscriber>> subs = subscribers; | |||||
for (final WeakReference<Subscriber> subscriber : subs) { | |||||
if (subscriber != null && subscriber.get() != null) | |||||
subscriber.get().onTopicPublished(this); | |||||
} | |||||
} | |||||
public Object[] getResults() { | |||||
return results; | |||||
} | |||||
public boolean isPending() { | |||||
return state == PENDING; | |||||
} | |||||
public void setPending() { | |||||
state = PENDING; | |||||
} | |||||
public interface Subscriber { | |||||
default void subscribeTopics() { | |||||
for (final Topic topic : getSubscription()) { | |||||
if (topic.isPublished()) { | |||||
onTopicPublished(topic); | |||||
} | |||||
topic.subscribe(this); | |||||
} | |||||
} | |||||
default void unsubscribeTopics() { | |||||
for (final Topic event : getSubscription()) { | |||||
event.unsubscribe(this); | |||||
} | |||||
} | |||||
void onTopicPublished(Topic topic); | |||||
Topic[] getSubscription(); | |||||
} | |||||
private static class SyncArrayList<E> extends ArrayList<E> { | |||||
@Override | |||||
public synchronized boolean add(final E e) { | |||||
return super.add(e); | |||||
} | |||||
} | |||||
} |