diff options
| author | 2019-01-22 18:21:01 +0000 | |
|---|---|---|
| committer | 2019-01-22 18:21:01 +0000 | |
| commit | a0602f3ebaa411ccc0e30b067437983167de3642 (patch) | |
| tree | 78189f73301af817be51ce8e14556651a9739aa3 | |
| parent | 46d74762f31b4136062bf443e89114553419d5fd (diff) | |
| parent | 15b4af194327d3a132503181917e8cb91e1387a8 (diff) | |
Merge "Move plugin and settings logic out of KeyguardClockSwitch."
4 files changed, 230 insertions, 132 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 1aff3949a74b..7218acf614d4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -1,15 +1,9 @@ package com.android.keyguard; -import android.content.ContentResolver; import android.content.Context; -import android.database.ContentObserver; import android.graphics.Paint; import android.graphics.Paint.Style; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; import android.util.AttributeSet; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -18,29 +12,19 @@ import android.widget.TextClock; import androidx.annotation.VisibleForTesting; -import com.android.keyguard.clock.BubbleClockController; -import com.android.keyguard.clock.StretchAnalogClockController; -import com.android.keyguard.clock.TypeClockController; +import com.android.keyguard.clock.ClockManager; import com.android.systemui.Dependency; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.ExtensionController.Extension; -import java.util.Objects; import java.util.TimeZone; -import java.util.function.Consumer; -import java.util.function.Supplier; /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. */ public class KeyguardClockSwitch extends RelativeLayout { - private LayoutInflater mLayoutInflater; - - private final ContentResolver mContentResolver; /** * Optional/alternative clock injected via plugin. */ @@ -63,14 +47,6 @@ public class KeyguardClockSwitch extends RelativeLayout { */ private View mKeyguardStatusArea; /** - * Used to select between plugin or default implementations of ClockPlugin interface. - */ - private Extension<ClockPlugin> mClockExtension; - /** - * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads. - */ - private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin); - /** * Maintain state so that a newly connected plugin can be initialized. */ private float mDarkAmount; @@ -94,16 +70,7 @@ public class KeyguardClockSwitch extends RelativeLayout { } }; - private final ContentObserver mContentObserver = - new ContentObserver(new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange) { - super.onChange(selfChange); - if (mClockExtension != null) { - mClockExtension.reload(); - } - } - }; + private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; public KeyguardClockSwitch(Context context) { this(context, null); @@ -111,8 +78,6 @@ public class KeyguardClockSwitch extends RelativeLayout { public KeyguardClockSwitch(Context context, AttributeSet attrs) { super(context, attrs); - mLayoutInflater = LayoutInflater.from(context); - mContentResolver = context.getContentResolver(); } /** @@ -133,45 +98,14 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class) - .withPlugin(ClockPlugin.class) - .withCallback(mClockPluginConsumer) - // Using withDefault even though this isn't the default as a workaround. - // ExtensionBulider doesn't provide the ability to supply a ClockPlugin - // instance based off of the value of a setting. Since multiple "default" - // can be provided, using a supplier that changes the settings value. - // A null return will cause Extension#reload to look at the next "default" - // supplier. - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - BubbleClockController.class.getName(), - () -> BubbleClockController.build(mLayoutInflater))) - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - StretchAnalogClockController.class.getName(), - () -> StretchAnalogClockController.build(mLayoutInflater))) - .withDefault( - new SettingsGattedSupplier( - mContentResolver, - Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, - TypeClockController.class.getName(), - () -> TypeClockController.build(mLayoutInflater))) - .build(); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), - false, mContentObserver); + Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener); Dependency.get(StatusBarStateController.class).addCallback(mStateListener); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mClockExtension.destroy(); - mContentResolver.unregisterContentObserver(mContentObserver); + Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener); Dependency.get(StatusBarStateController.class).removeCallback(mStateListener); } @@ -313,52 +247,12 @@ public class KeyguardClockSwitch extends RelativeLayout { } @VisibleForTesting (otherwise = VisibleForTesting.NONE) - Consumer<ClockPlugin> getClockPluginConsumer() { - return mClockPluginConsumer; + ClockManager.ClockChangedListener getClockChangedListener() { + return mClockChangedListener; } @VisibleForTesting (otherwise = VisibleForTesting.NONE) StatusBarStateController.StateListener getStateListener() { return mStateListener; } - - /** - * Supplier that only gets an instance when a settings value matches expected value. - */ - private static class SettingsGattedSupplier implements Supplier<ClockPlugin> { - - private final ContentResolver mContentResolver; - private final String mKey; - private final String mValue; - private final Supplier<ClockPlugin> mSupplier; - - /** - * Constructs a supplier that changes secure setting key against value. - * - * @param contentResolver Used to look up settings value. - * @param key Settings key. - * @param value If the setting matches this values that get supplies a ClockPlugin - * instance. - * @param supplier Supplier of ClockPlugin instance, only used if the setting - * matches value. - */ - SettingsGattedSupplier(ContentResolver contentResolver, String key, String value, - Supplier<ClockPlugin> supplier) { - mContentResolver = contentResolver; - mKey = key; - mValue = value; - mSupplier = supplier; - } - - /** - * Returns null if the settings value doesn't match the expected value. - * - * A null return causes Extension#reload to skip this supplier and move to the next. - */ - @Override - public ClockPlugin get() { - final String currentValue = Settings.Secure.getString(mContentResolver, mKey); - return Objects.equals(currentValue, mValue) ? mSupplier.get() : null; - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java new file mode 100644 index 000000000000..3217ca6f489c --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard.clock; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.view.LayoutInflater; + +import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.ExtensionController.Extension; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Manages custom clock faces. + */ +@Singleton +public final class ClockManager { + + private final LayoutInflater mLayoutInflater; + private final ContentResolver mContentResolver; + + /** + * Observe settings changes to know when to switch the clock face. + */ + private final ContentObserver mContentObserver = + new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + if (mClockExtension != null) { + mClockExtension.reload(); + } + } + }; + + private final ExtensionController mExtensionController; + /** + * Used to select between plugin or default implementations of ClockPlugin interface. + */ + private Extension<ClockPlugin> mClockExtension; + /** + * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads. + */ + private final Consumer<ClockPlugin> mClockPluginConsumer = this::setClockPlugin; + + private final List<ClockChangedListener> mListeners = new ArrayList<>(); + + @Inject + public ClockManager(Context context, ExtensionController extensionController) { + mExtensionController = extensionController; + mLayoutInflater = LayoutInflater.from(context); + mContentResolver = context.getContentResolver(); + } + + /** + * Add listener to be notified when clock implementation should change. + */ + public void addOnClockChangedListener(ClockChangedListener listener) { + if (mListeners.isEmpty()) { + register(); + } + mListeners.add(listener); + if (mClockExtension != null) { + mClockExtension.reload(); + } + } + + /** + * Remove listener added with {@link addOnClockChangedListener}. + */ + public void removeOnClockChangedListener(ClockChangedListener listener) { + mListeners.remove(listener); + if (mListeners.isEmpty()) { + unregister(); + } + } + + private void setClockPlugin(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 register() { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), + false, mContentObserver); + mClockExtension = mExtensionController.newExtension(ClockPlugin.class) + .withPlugin(ClockPlugin.class) + .withCallback(mClockPluginConsumer) + // Using withDefault even though this isn't the default as a workaround. + // ExtensionBuilder doesn't provide the ability to supply a ClockPlugin + // instance based off of the value of a setting. Since multiple "default" + // can be provided, using a supplier that changes the settings value. + // A null return will cause Extension#reload to look at the next "default" + // supplier. + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + BubbleClockController.class.getName(), + () -> BubbleClockController.build(mLayoutInflater))) + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + StretchAnalogClockController.class.getName(), + () -> StretchAnalogClockController.build(mLayoutInflater))) + .withDefault( + new SettingsGattedSupplier( + mContentResolver, + Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, + TypeClockController.class.getName(), + () -> TypeClockController.build(mLayoutInflater))) + .build(); + } + + private void unregister() { + mContentResolver.unregisterContentObserver(mContentObserver); + mClockExtension.destroy(); + } + + /** + * Listener for events that should cause the custom clock face to change. + */ + public interface ClockChangedListener { + /** + * Called when custom clock should change. + * + * @param clock Custom clock face to use. A null value indicates the default clock face. + */ + void onClockChanged(ClockPlugin clock); + } + + /** + * Supplier that only gets an instance when a settings value matches expected value. + */ + private static class SettingsGattedSupplier implements Supplier<ClockPlugin> { + + private final ContentResolver mContentResolver; + private final String mKey; + private final String mValue; + private final Supplier<ClockPlugin> mSupplier; + + /** + * Constructs a supplier that changes secure setting key against value. + * + * @param contentResolver Used to look up settings value. + * @param key Settings key. + * @param value If the setting matches this values that get supplies a ClockPlugin + * instance. + * @param supplier Supplier of ClockPlugin instance, only used if the setting + * matches value. + */ + SettingsGattedSupplier(ContentResolver contentResolver, String key, String value, + Supplier<ClockPlugin> supplier) { + mContentResolver = contentResolver; + mKey = key; + mValue = value; + mSupplier = supplier; + } + + /** + * Returns null if the settings value doesn't match the expected value. + * + * A null return causes Extension#reload to skip this supplier and move to the next. + */ + @Override + public ClockPlugin get() { + final String currentValue = Settings.Secure.getString(mContentResolver, mKey); + return Objects.equals(currentValue, mValue) ? mSupplier.get() : null; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index ec6ecc64d07e..d99f234c26c8 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -29,6 +29,7 @@ import com.android.internal.app.ColorDisplayController; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; +import com.android.keyguard.clock.ClockManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.appops.AppOpsController; import com.android.systemui.assist.AssistManager; @@ -283,6 +284,7 @@ public class Dependency extends SystemUI { @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; @Nullable @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; + @Inject Lazy<ClockManager> mClockManager; @Inject public Dependency() { @@ -449,6 +451,7 @@ public class Dependency extends SystemUI { mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get); mProviders.put(ForegroundServiceNotificationListener.class, mForegroundServiceNotificationListener::get); + mProviders.put(ClockManager.class, mClockManager::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index fbc1c20755a1..d80b444ad64b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -39,6 +39,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextClock; +import com.android.keyguard.clock.ClockManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ClockPlugin; import com.android.systemui.statusbar.StatusBarState; @@ -51,8 +52,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.function.Consumer; - @SmallTest @RunWith(AndroidTestingRunner.class) // Need to run on the main thread because KeyguardSliceView$Row init checks for @@ -85,7 +84,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); verify(mClockView).setVisibility(GONE); assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer); @@ -102,7 +101,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); // WHEN the plugin is connected - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); // THEN the big clock container is visible and it is the parent of the // big clock view. assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE); @@ -112,7 +111,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginConnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); verify(mClockView, never()).setVisibility(GONE); } @@ -121,11 +120,11 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // GIVEN a plugin has already connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin1); // WHEN a second plugin is connected ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin2); // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer); assertThat(plugin1.getView().getParent()).isNull(); @@ -137,7 +136,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { mKeyguardClockSwitch.setDarkAmount(0.5f); // WHEN a plugin is connected ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); // THEN dark amount should be initalized on the plugin. verify(plugin).setDarkAmount(0.5f); } @@ -149,8 +148,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { when(plugin.getView()).thenReturn(pluginView); mClockView.setVisibility(GONE); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); - mKeyguardClockSwitch.getClockPluginConsumer().accept(null); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null); verify(mClockView).setVisibility(VISIBLE); assertThat(plugin.getView().getParent()).isNull(); @@ -167,8 +166,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getBigClockView()).thenReturn(pluginView); // WHEN the plugin is connected and then disconnected - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); - mKeyguardClockSwitch.getClockPluginConsumer().accept(null); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null); // THEN the big lock container is GONE and the big clock view doesn't have // a parent. assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE); @@ -178,8 +177,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Test public void onPluginDisconnected_nullView() { ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); - mKeyguardClockSwitch.getClockPluginConsumer().accept(null); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null); verify(mClockView, never()).setVisibility(GONE); } @@ -188,13 +187,13 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // GIVEN two plugins are connected ClockPlugin plugin1 = mock(ClockPlugin.class); when(plugin1.getView()).thenReturn(new TextClock(getContext())); - Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer(); - consumer.accept(plugin1); + ClockManager.ClockChangedListener listener = mKeyguardClockSwitch.getClockChangedListener(); + listener.onClockChanged(plugin1); ClockPlugin plugin2 = mock(ClockPlugin.class); when(plugin2.getView()).thenReturn(new TextClock(getContext())); - consumer.accept(plugin2); + listener.onClockChanged(plugin2); // WHEN the second plugin is disconnected - consumer.accept(null); + listener.onClockChanged(null); // THEN the default clock should be shown. verify(mClockView).setVisibility(VISIBLE); assertThat(plugin1.getView().getParent()).isNull(); @@ -213,7 +212,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { ClockPlugin plugin = mock(ClockPlugin.class); TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); mKeyguardClockSwitch.setTextColor(Color.WHITE); @@ -237,7 +236,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { TextClock pluginView = new TextClock(getContext()); when(plugin.getView()).thenReturn(pluginView); Style style = mock(Style.class); - mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin); + mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin); mKeyguardClockSwitch.setStyle(style); |