Loading packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +6 −112 Original line number Diff line number Diff line 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; Loading @@ -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. */ Loading @@ -62,14 +46,6 @@ public class KeyguardClockSwitch extends RelativeLayout { * or not to show it below the alternate clock. */ 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. */ Loading @@ -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); Loading @@ -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(); } /** Loading @@ -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); } Loading Loading @@ -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; } } } packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java 0 → 100644 +202 −0 Original line number Diff line number Diff line /* * 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; } } } packages/SystemUI/src/com/android/systemui/Dependency.java +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +19 −20 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); Loading @@ -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); Loading @@ -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); } Loading @@ -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(); Loading @@ -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); } Loading @@ -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(); Loading @@ -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); Loading @@ -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); } Loading @@ -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(); Loading @@ -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); Loading @@ -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); Loading Loading
packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +6 −112 Original line number Diff line number Diff line 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; Loading @@ -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. */ Loading @@ -62,14 +46,6 @@ public class KeyguardClockSwitch extends RelativeLayout { * or not to show it below the alternate clock. */ 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. */ Loading @@ -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); Loading @@ -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(); } /** Loading @@ -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); } Loading Loading @@ -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; } } }
packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java 0 → 100644 +202 −0 Original line number Diff line number Diff line /* * 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; } } }
packages/SystemUI/src/com/android/systemui/Dependency.java +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { Loading Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +19 −20 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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); Loading @@ -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); Loading @@ -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); } Loading @@ -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(); Loading @@ -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); } Loading @@ -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(); Loading @@ -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); Loading @@ -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); } Loading @@ -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(); Loading @@ -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); Loading @@ -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); Loading