ClockManager listeners receive separate ClockPlugin instances.
Fixes: 129133354 Test: atest ActivityManagerDisplayKeyguardTests.java with custom clock. Change-Id: Ic7447c1bbf2a66d82f2b11062e5ca4a5de846a4e
This commit is contained in:
@@ -41,6 +41,7 @@ import com.android.systemui.util.InjectionInflationController;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -52,14 +53,9 @@ import javax.inject.Singleton;
|
||||
public final class ClockManager {
|
||||
|
||||
private static final String TAG = "ClockOptsProvider";
|
||||
private static final String DEFAULT_CLOCK_ID = "default";
|
||||
|
||||
private final List<ClockInfo> mClockInfos = new ArrayList<>();
|
||||
/**
|
||||
* Map from expected value stored in settings to supplier of custom clock face.
|
||||
*/
|
||||
private final Map<String, ClockPlugin> mClocks = new ArrayMap<>();
|
||||
@Nullable private ClockPlugin mCurrentClock;
|
||||
private final AvailableClocks mPreviewClocks;
|
||||
private final List<Supplier<ClockPlugin>> mBuiltinClocks = new ArrayList<>();
|
||||
|
||||
private final Context mContext;
|
||||
private final ContentResolver mContentResolver;
|
||||
@@ -78,21 +74,6 @@ public final class ClockManager {
|
||||
}
|
||||
};
|
||||
|
||||
private final PluginListener<ClockPlugin> mClockPluginListener =
|
||||
new PluginListener<ClockPlugin>() {
|
||||
@Override
|
||||
public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
|
||||
addClockPlugin(plugin);
|
||||
reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginDisconnected(ClockPlugin plugin) {
|
||||
removeClockPlugin(plugin);
|
||||
reload();
|
||||
}
|
||||
};
|
||||
|
||||
private final PluginManager mPluginManager;
|
||||
|
||||
/**
|
||||
@@ -108,13 +89,22 @@ public final class ClockManager {
|
||||
}
|
||||
};
|
||||
@Nullable private DockManager mDockManager;
|
||||
|
||||
/**
|
||||
* When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
|
||||
* to show.
|
||||
*/
|
||||
private boolean mIsDocked;
|
||||
|
||||
private final List<ClockChangedListener> mListeners = new ArrayList<>();
|
||||
/**
|
||||
* Listeners for onClockChanged event.
|
||||
*
|
||||
* Each listener must receive a separate clock plugin instance. Otherwise, there could be
|
||||
* problems like attempting to attach a view that already has a parent. To deal with this issue,
|
||||
* each listener is associated with a collection of available clocks. When onClockChanged is
|
||||
* fired the current clock plugin instance is retrieved from that listeners available clocks.
|
||||
*/
|
||||
private final Map<ClockChangedListener, AvailableClocks> mListeners = new ArrayMap<>();
|
||||
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
@@ -133,14 +123,16 @@ public final class ClockManager {
|
||||
mPluginManager = pluginManager;
|
||||
mContentResolver = contentResolver;
|
||||
mSettingsWrapper = settingsWrapper;
|
||||
mPreviewClocks = new AvailableClocks();
|
||||
|
||||
Resources res = context.getResources();
|
||||
LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context));
|
||||
|
||||
addClockPlugin(new DefaultClockController(res, layoutInflater, colorExtractor));
|
||||
addClockPlugin(new BubbleClockController(res, layoutInflater, colorExtractor));
|
||||
addClockPlugin(new StretchAnalogClockController(res, layoutInflater, colorExtractor));
|
||||
addClockPlugin(new TypeClockController(res, layoutInflater, colorExtractor));
|
||||
addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
|
||||
addBuiltinClock(() -> new BubbleClockController(res, layoutInflater, colorExtractor));
|
||||
addBuiltinClock(() -> new StretchAnalogClockController(res, layoutInflater,
|
||||
colorExtractor));
|
||||
addBuiltinClock(() -> new TypeClockController(res, layoutInflater, colorExtractor));
|
||||
|
||||
// Store the size of the display for generation of clock preview.
|
||||
DisplayMetrics dm = res.getDisplayMetrics();
|
||||
@@ -155,7 +147,12 @@ public final class ClockManager {
|
||||
if (mListeners.isEmpty()) {
|
||||
register();
|
||||
}
|
||||
mListeners.add(listener);
|
||||
AvailableClocks availableClocks = new AvailableClocks();
|
||||
for (int i = 0; i < mBuiltinClocks.size(); i++) {
|
||||
availableClocks.addClockPlugin(mBuiltinClocks.get(i).get());
|
||||
}
|
||||
mListeners.put(listener, availableClocks);
|
||||
mPluginManager.addPluginListener(availableClocks, ClockPlugin.class, true);
|
||||
reload();
|
||||
}
|
||||
|
||||
@@ -163,7 +160,8 @@ public final class ClockManager {
|
||||
* Remove listener added with {@link addOnClockChangedListener}.
|
||||
*/
|
||||
public void removeOnClockChangedListener(ClockChangedListener listener) {
|
||||
mListeners.remove(listener);
|
||||
AvailableClocks availableClocks = mListeners.remove(listener);
|
||||
mPluginManager.removePluginListener(availableClocks);
|
||||
if (mListeners.isEmpty()) {
|
||||
unregister();
|
||||
}
|
||||
@@ -173,16 +171,16 @@ public final class ClockManager {
|
||||
* Get information about available clock faces.
|
||||
*/
|
||||
List<ClockInfo> getClockInfos() {
|
||||
return mClockInfos;
|
||||
return mPreviewClocks.getInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current clock.
|
||||
* @returns current custom clock or null for default.
|
||||
* @return current custom clock or null for default.
|
||||
*/
|
||||
@Nullable
|
||||
ClockPlugin getCurrentClock() {
|
||||
return mCurrentClock;
|
||||
return mPreviewClocks.getCurrentClock();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -195,39 +193,14 @@ public final class ClockManager {
|
||||
return mContentObserver;
|
||||
}
|
||||
|
||||
private void addClockPlugin(ClockPlugin plugin) {
|
||||
final String id = plugin.getClass().getName();
|
||||
mClocks.put(plugin.getClass().getName(), plugin);
|
||||
mClockInfos.add(ClockInfo.builder()
|
||||
.setName(plugin.getName())
|
||||
.setTitle(plugin.getTitle())
|
||||
.setId(id)
|
||||
.setThumbnail(() -> plugin.getThumbnail())
|
||||
.setPreview(() -> plugin.getPreview(mWidth, mHeight))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void removeClockPlugin(ClockPlugin plugin) {
|
||||
final String id = plugin.getClass().getName();
|
||||
mClocks.remove(id);
|
||||
for (int i = 0; i < mClockInfos.size(); i++) {
|
||||
if (id.equals(mClockInfos.get(i).getId())) {
|
||||
mClockInfos.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyClockChanged(ClockPlugin plugin) {
|
||||
for (int i = 0; i < mListeners.size(); i++) {
|
||||
// It probably doesn't make sense to supply the same plugin instances to multiple
|
||||
// listeners. This should be fine for now since there is only a single listener.
|
||||
mListeners.get(i).onClockChanged(plugin);
|
||||
}
|
||||
private void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
|
||||
ClockPlugin plugin = pluginSupplier.get();
|
||||
mPreviewClocks.addClockPlugin(plugin);
|
||||
mBuiltinClocks.add(pluginSupplier);
|
||||
}
|
||||
|
||||
private void register() {
|
||||
mPluginManager.addPluginListener(mClockPluginListener, ClockPlugin.class, true);
|
||||
mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true);
|
||||
mContentResolver.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
|
||||
false, mContentObserver);
|
||||
@@ -243,7 +216,7 @@ public final class ClockManager {
|
||||
}
|
||||
|
||||
private void unregister() {
|
||||
mPluginManager.removePluginListener(mClockPluginListener);
|
||||
mPluginManager.removePluginListener(mPreviewClocks);
|
||||
mContentResolver.unregisterContentObserver(mContentObserver);
|
||||
if (mDockManager != null) {
|
||||
mDockManager.removeListener(mDockEventListener);
|
||||
@@ -251,30 +224,16 @@ public final class ClockManager {
|
||||
}
|
||||
|
||||
private void reload() {
|
||||
mCurrentClock = getClockPlugin();
|
||||
if (mCurrentClock instanceof DefaultClockController) {
|
||||
notifyClockChanged(null);
|
||||
} else {
|
||||
notifyClockChanged(mCurrentClock);
|
||||
}
|
||||
}
|
||||
|
||||
private ClockPlugin getClockPlugin() {
|
||||
ClockPlugin plugin = null;
|
||||
if (mIsDocked) {
|
||||
final String name = mSettingsWrapper.getDockedClockFace();
|
||||
if (name != null) {
|
||||
plugin = mClocks.get(name);
|
||||
if (plugin != null) {
|
||||
return plugin;
|
||||
}
|
||||
mPreviewClocks.reload();
|
||||
mListeners.forEach((listener, clocks) -> {
|
||||
clocks.reload();
|
||||
ClockPlugin clock = clocks.getCurrentClock();
|
||||
if (clock instanceof DefaultClockController) {
|
||||
listener.onClockChanged(null);
|
||||
} else {
|
||||
listener.onClockChanged(clock);
|
||||
}
|
||||
}
|
||||
final String name = mSettingsWrapper.getLockScreenCustomClockFace();
|
||||
if (name != null) {
|
||||
plugin = mClocks.get(name);
|
||||
}
|
||||
return plugin;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,4 +247,106 @@ public final class ClockManager {
|
||||
*/
|
||||
void onClockChanged(ClockPlugin clock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of available clocks.
|
||||
*/
|
||||
private final class AvailableClocks implements PluginListener<ClockPlugin> {
|
||||
|
||||
/**
|
||||
* Map from expected value stored in settings to plugin for custom clock face.
|
||||
*/
|
||||
private final Map<String, ClockPlugin> mClocks = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Metadata about available clocks, such as name and preview images.
|
||||
*/
|
||||
private final List<ClockInfo> mClockInfo = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Active ClockPlugin.
|
||||
*/
|
||||
@Nullable private ClockPlugin mCurrentClock;
|
||||
|
||||
@Override
|
||||
public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
|
||||
addClockPlugin(plugin);
|
||||
reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginDisconnected(ClockPlugin plugin) {
|
||||
removeClockPlugin(plugin);
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current clock.
|
||||
* @return current custom clock or null for default.
|
||||
*/
|
||||
@Nullable
|
||||
ClockPlugin getCurrentClock() {
|
||||
return mCurrentClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about available clock faces.
|
||||
*/
|
||||
List<ClockInfo> getInfo() {
|
||||
return mClockInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a clock plugin to the collection of available clocks.
|
||||
*
|
||||
* @param plugin The plugin to add.
|
||||
*/
|
||||
void addClockPlugin(ClockPlugin plugin) {
|
||||
final String id = plugin.getClass().getName();
|
||||
mClocks.put(plugin.getClass().getName(), plugin);
|
||||
mClockInfo.add(ClockInfo.builder()
|
||||
.setName(plugin.getName())
|
||||
.setTitle(plugin.getTitle())
|
||||
.setId(id)
|
||||
.setThumbnail(plugin::getThumbnail)
|
||||
.setPreview(() -> plugin.getPreview(mWidth, mHeight))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void removeClockPlugin(ClockPlugin plugin) {
|
||||
final String id = plugin.getClass().getName();
|
||||
mClocks.remove(id);
|
||||
for (int i = 0; i < mClockInfo.size(); i++) {
|
||||
if (id.equals(mClockInfo.get(i).getId())) {
|
||||
mClockInfo.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current clock.
|
||||
*/
|
||||
void reload() {
|
||||
mCurrentClock = getClockPlugin();
|
||||
}
|
||||
|
||||
private ClockPlugin getClockPlugin() {
|
||||
ClockPlugin plugin = null;
|
||||
if (ClockManager.this.isDocked()) {
|
||||
final String name = mSettingsWrapper.getDockedClockFace();
|
||||
if (name != null) {
|
||||
plugin = mClocks.get(name);
|
||||
if (plugin != null) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
final String name = mSettingsWrapper.getLockScreenCustomClockFace();
|
||||
if (name != null) {
|
||||
plugin = mClocks.get(name);
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.android.keyguard.clock;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
@@ -31,6 +33,7 @@ import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
||||
import com.android.systemui.dock.DockManager;
|
||||
import com.android.systemui.dock.DockManagerFake;
|
||||
import com.android.systemui.plugins.ClockPlugin;
|
||||
import com.android.systemui.shared.plugins.PluginManager;
|
||||
import com.android.systemui.util.InjectionInflationController;
|
||||
|
||||
@@ -38,6 +41,7 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@@ -57,7 +61,8 @@ public final class ClockManagerTest extends SysuiTestCase {
|
||||
@Mock SysuiColorExtractor mMockColorExtractor;
|
||||
@Mock ContentResolver mMockContentResolver;
|
||||
@Mock SettingsWrapper mMockSettingsWrapper;
|
||||
@Mock ClockManager.ClockChangedListener mMockListener;
|
||||
@Mock ClockManager.ClockChangedListener mMockListener1;
|
||||
@Mock ClockManager.ClockChangedListener mMockListener2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -73,13 +78,17 @@ public final class ClockManagerTest extends SysuiTestCase {
|
||||
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
|
||||
mMockSettingsWrapper);
|
||||
|
||||
mClockManager.addOnClockChangedListener(mMockListener);
|
||||
mClockManager.addOnClockChangedListener(mMockListener1);
|
||||
mClockManager.addOnClockChangedListener(mMockListener2);
|
||||
reset(mMockListener1, mMockListener2);
|
||||
|
||||
mContentObserver = mClockManager.getContentObserver();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mClockManager.removeOnClockChangedListener(mMockListener);
|
||||
mClockManager.removeOnClockChangedListener(mMockListener1);
|
||||
mClockManager.removeOnClockChangedListener(mMockListener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,6 +124,34 @@ public final class ClockManagerTest extends SysuiTestCase {
|
||||
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onClockChanged_customClock() {
|
||||
// GIVEN that settings is set to the bubble clock face
|
||||
when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
|
||||
// WHEN settings change event is fired
|
||||
mContentObserver.onChange(false);
|
||||
// THEN the plugin is the bubble clock face.
|
||||
ArgumentCaptor<ClockPlugin> captor = ArgumentCaptor.forClass(ClockPlugin.class);
|
||||
verify(mMockListener1).onClockChanged(captor.capture());
|
||||
assertThat(captor.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onClockChanged_uniqueInstances() {
|
||||
// GIVEN that settings is set to the bubble clock face
|
||||
when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
|
||||
// WHEN settings change event is fired
|
||||
mContentObserver.onChange(false);
|
||||
// THEN the listeners receive separate instances of the Bubble clock plugin.
|
||||
ArgumentCaptor<ClockPlugin> captor1 = ArgumentCaptor.forClass(ClockPlugin.class);
|
||||
ArgumentCaptor<ClockPlugin> captor2 = ArgumentCaptor.forClass(ClockPlugin.class);
|
||||
verify(mMockListener1).onClockChanged(captor1.capture());
|
||||
verify(mMockListener2).onClockChanged(captor2.capture());
|
||||
assertThat(captor1.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
|
||||
assertThat(captor2.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
|
||||
assertThat(captor1.getValue()).isNotSameAs(captor2.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentClock_badSettingsValue() {
|
||||
// GIVEN that settings contains a value that doesn't correspond to a
|
||||
|
||||
Reference in New Issue
Block a user