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.RootShell; | |||
import com.wireguard.android.util.ToolsInstaller; | |||
import com.wireguard.android.util.Topic; | |||
import java.io.File; | |||
import java.util.concurrent.Executor; | |||
@@ -68,8 +67,6 @@ public class Application extends android.app.Application { | |||
ToolsInstaller getToolsInstaller(); | |||
TunnelManager getTunnelManager(); | |||
Topic getThemeChangeTopic(); | |||
} | |||
@Qualifier | |||
@@ -119,13 +116,6 @@ public class Application extends android.app.Application { | |||
return new FileConfigStore(context); | |||
} | |||
@ApplicationScope | |||
@Provides | |||
public static Topic getThemeChangeTopic() { | |||
return new Topic(); | |||
} | |||
@ApplicationScope | |||
@Provides | |||
public static Executor getExecutor() { | |||
@@ -11,24 +11,19 @@ import android.databinding.CallbackRegistry; | |||
import android.databinding.CallbackRegistry.NotifierCallback; | |||
import android.os.Bundle; | |||
import android.support.v7.app.AppCompatActivity; | |||
import android.util.Log; | |||
import com.wireguard.android.Application; | |||
import com.wireguard.android.backend.GoBackend; | |||
import com.wireguard.android.model.Tunnel; | |||
import com.wireguard.android.model.TunnelManager; | |||
import com.wireguard.android.util.Topic; | |||
import java.lang.reflect.Field; | |||
import java.util.Objects; | |||
/** | |||
* 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 final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry(); | |||
@@ -45,8 +40,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Su | |||
@Override | |||
protected void onCreate(final Bundle savedInstanceState) { | |||
subscribeTopics(); | |||
// Restore the saved tunnel if there is one; otherwise grab it from the arguments. | |||
String savedTunnelName = 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 | |||
protected void onSaveInstanceState(final Bundle outState) { | |||
if (selectedTunnel != null) | |||
@@ -118,35 +105,4 @@ public abstract class BaseActivity extends AppCompatActivity implements Topic.Su | |||
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.R; | |||
import com.wireguard.android.backend.WgQuickBackend; | |||
import com.wireguard.android.util.Topic; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
@@ -33,7 +32,7 @@ import java.util.Map; | |||
* 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 int permissionRequestCounter; | |||
@@ -59,7 +58,6 @@ public class SettingsActivity extends AppCompatActivity implements Topic.Subscri | |||
@Override | |||
protected void onCreate(final Bundle savedInstanceState) { | |||
super.onCreate(savedInstanceState); | |||
subscribeTopics(); | |||
if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) { | |||
getSupportFragmentManager().beginTransaction() | |||
.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 | |||
public boolean onOptionsItemSelected(final MenuItem item) { | |||
switch (item.getItemId()) { | |||
@@ -99,18 +91,7 @@ public class SettingsActivity extends AppCompatActivity implements Topic.Subscri | |||
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 | |||
public void onCreatePreferences(final Bundle savedInstanceState, final String key) { | |||
addPreferencesFromResource(R.xml.preferences); | |||
@@ -121,26 +102,5 @@ public class SettingsActivity extends AppCompatActivity implements Topic.Subscri | |||
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); | |||
} | |||
} | |||
} |