blob: 9f4c403ed78741d2264c0d24c82ba96c13a4bb2e [file] [log] [blame]
Robert Snoeberger15b4af12019-01-18 15:37:27 -05001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.keyguard.clock;
17
Robert Snoebergerb300a4e2019-02-13 20:13:53 +000018import android.annotation.Nullable;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050019import android.content.ContentResolver;
20import android.content.Context;
Robert Snoeberger6b244b02019-02-04 15:33:31 -050021import android.content.res.Resources;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050022import android.database.ContentObserver;
Robert Snoeberger372e13f2019-04-08 17:07:14 -040023import android.net.Uri;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050024import android.os.Handler;
25import android.os.Looper;
Robert Snoeberger372e13f2019-04-08 17:07:14 -040026import android.os.UserHandle;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050027import android.provider.Settings;
Robert Snoeberger71e50792019-02-15 15:48:01 -050028import android.util.ArrayMap;
29import android.util.DisplayMetrics;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050030import android.view.LayoutInflater;
31
Robert Snoebergerb300a4e2019-02-13 20:13:53 +000032import androidx.annotation.VisibleForTesting;
Robert Snoebergerbe2a1882019-04-10 13:48:02 -040033import androidx.lifecycle.Observer;
Robert Snoebergerb300a4e2019-02-13 20:13:53 +000034
Robert Snoeberger71e50792019-02-15 15:48:01 -050035import com.android.systemui.colorextraction.SysuiColorExtractor;
Robert Snoebergerb300a4e2019-02-13 20:13:53 +000036import com.android.systemui.dock.DockManager;
37import com.android.systemui.dock.DockManager.DockEventListener;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050038import com.android.systemui.plugins.ClockPlugin;
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050039import com.android.systemui.plugins.PluginListener;
Robert Snoebergerbe2a1882019-04-10 13:48:02 -040040import com.android.systemui.settings.CurrentUserObservable;
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050041import com.android.systemui.shared.plugins.PluginManager;
Robert Snoeberger71e50792019-02-15 15:48:01 -050042import com.android.systemui.util.InjectionInflationController;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050043
44import java.util.ArrayList;
45import java.util.List;
Robert Snoeberger71e50792019-02-15 15:48:01 -050046import java.util.Map;
Robert Snoebergerbe2a1882019-04-10 13:48:02 -040047import java.util.Objects;
Robert Snoebergerd8445ea2019-03-27 10:41:23 -040048import java.util.function.Supplier;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050049
50import javax.inject.Inject;
51import javax.inject.Singleton;
52
53/**
Robert Snoeberger71e50792019-02-15 15:48:01 -050054 * Manages custom clock faces for AOD and lock screen.
Robert Snoeberger15b4af12019-01-18 15:37:27 -050055 */
56@Singleton
57public final class ClockManager {
58
Robert Snoeberger00257512019-02-27 16:44:04 -050059 private static final String TAG = "ClockOptsProvider";
Robert Snoeberger00257512019-02-27 16:44:04 -050060
Robert Snoebergerd8445ea2019-03-27 10:41:23 -040061 private final AvailableClocks mPreviewClocks;
62 private final List<Supplier<ClockPlugin>> mBuiltinClocks = new ArrayList<>();
Robert Snoeberger71e50792019-02-15 15:48:01 -050063
Robert Snoeberger3f4650c2019-03-25 14:41:42 -040064 private final Context mContext;
Robert Snoeberger71e50792019-02-15 15:48:01 -050065 private final ContentResolver mContentResolver;
66 private final SettingsWrapper mSettingsWrapper;
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050067 private final Handler mMainHandler = new Handler(Looper.getMainLooper());
Robert Snoebergerbe2a1882019-04-10 13:48:02 -040068 private final CurrentUserObservable mCurrentUserObservable;
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050069
Robert Snoeberger71e50792019-02-15 15:48:01 -050070 /**
Robert Snoeberger15b4af12019-01-18 15:37:27 -050071 * Observe settings changes to know when to switch the clock face.
72 */
73 private final ContentObserver mContentObserver =
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050074 new ContentObserver(mMainHandler) {
Robert Snoeberger15b4af12019-01-18 15:37:27 -050075 @Override
Robert Snoeberger372e13f2019-04-08 17:07:14 -040076 public void onChange(boolean selfChange, Uri uri, int userId) {
77 super.onChange(selfChange, uri, userId);
Robert Snoebergerbe2a1882019-04-10 13:48:02 -040078 if (Objects.equals(userId,
79 mCurrentUserObservable.getCurrentUser().getValue())) {
Robert Snoeberger372e13f2019-04-08 17:07:14 -040080 reload();
81 }
Robert Snoeberger15b4af12019-01-18 15:37:27 -050082 }
83 };
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050084
Robert Snoebergerbe2a1882019-04-10 13:48:02 -040085 /**
86 * Observe user changes and react by potentially loading the custom clock for the new user.
87 */
88 private final Observer<Integer> mCurrentUserObserver = (newUserId) -> reload();
89
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050090 private final PluginManager mPluginManager;
Lucas Dupind93e95f2019-05-01 00:49:35 +000091 @Nullable private final DockManager mDockManager;
Robert Snoeberger9ad03f42019-02-28 14:47:49 -050092
Robert Snoebergerb300a4e2019-02-13 20:13:53 +000093 /**
94 * Observe changes to dock state to know when to switch the clock face.
95 */
96 private final DockEventListener mDockEventListener =
97 new DockEventListener() {
98 @Override
99 public void onEvent(int event) {
Robert Snoeberger71e50792019-02-15 15:48:01 -0500100 mIsDocked = (event == DockManager.STATE_DOCKED
Robert Snoebergerb300a4e2019-02-13 20:13:53 +0000101 || event == DockManager.STATE_DOCKED_HIDE);
Robert Snoeberger71e50792019-02-15 15:48:01 -0500102 reload();
Robert Snoebergerb300a4e2019-02-13 20:13:53 +0000103 }
104 };
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400105
Robert Snoeberger71e50792019-02-15 15:48:01 -0500106 /**
107 * When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
108 * to show.
109 */
110 private boolean mIsDocked;
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500111
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400112 /**
113 * Listeners for onClockChanged event.
114 *
115 * Each listener must receive a separate clock plugin instance. Otherwise, there could be
116 * problems like attempting to attach a view that already has a parent. To deal with this issue,
117 * each listener is associated with a collection of available clocks. When onClockChanged is
118 * fired the current clock plugin instance is retrieved from that listeners available clocks.
119 */
120 private final Map<ClockChangedListener, AvailableClocks> mListeners = new ArrayMap<>();
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500121
Robert Snoeberger71e50792019-02-15 15:48:01 -0500122 private final int mWidth;
123 private final int mHeight;
124
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500125 @Inject
Robert Snoeberger71e50792019-02-15 15:48:01 -0500126 public ClockManager(Context context, InjectionInflationController injectionInflater,
Lucas Dupind93e95f2019-05-01 00:49:35 +0000127 PluginManager pluginManager, SysuiColorExtractor colorExtractor,
128 @Nullable DockManager dockManager) {
Robert Snoeberger3f4650c2019-03-25 14:41:42 -0400129 this(context, injectionInflater, pluginManager, colorExtractor,
Robert Snoebergerbe2a1882019-04-10 13:48:02 -0400130 context.getContentResolver(), new CurrentUserObservable(context),
Lucas Dupind93e95f2019-05-01 00:49:35 +0000131 new SettingsWrapper(context.getContentResolver()), dockManager);
Robert Snoeberger71e50792019-02-15 15:48:01 -0500132 }
133
Lucas Dupind93e95f2019-05-01 00:49:35 +0000134 @VisibleForTesting
Robert Snoeberger71e50792019-02-15 15:48:01 -0500135 ClockManager(Context context, InjectionInflationController injectionInflater,
Robert Snoeberger3f4650c2019-03-25 14:41:42 -0400136 PluginManager pluginManager, SysuiColorExtractor colorExtractor,
Robert Snoebergerbe2a1882019-04-10 13:48:02 -0400137 ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
Lucas Dupind93e95f2019-05-01 00:49:35 +0000138 SettingsWrapper settingsWrapper, DockManager dockManager) {
Robert Snoeberger3f4650c2019-03-25 14:41:42 -0400139 mContext = context;
Robert Snoeberger9ad03f42019-02-28 14:47:49 -0500140 mPluginManager = pluginManager;
Robert Snoeberger71e50792019-02-15 15:48:01 -0500141 mContentResolver = contentResolver;
142 mSettingsWrapper = settingsWrapper;
Robert Snoebergerbe2a1882019-04-10 13:48:02 -0400143 mCurrentUserObservable = currentUserObservable;
Lucas Dupind93e95f2019-05-01 00:49:35 +0000144 mDockManager = dockManager;
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400145 mPreviewClocks = new AvailableClocks();
Robert Snoeberger6b244b02019-02-04 15:33:31 -0500146
147 Resources res = context.getResources();
Robert Snoeberger71e50792019-02-15 15:48:01 -0500148 LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context));
Robert Snoeberger9ad03f42019-02-28 14:47:49 -0500149
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400150 addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
151 addBuiltinClock(() -> new BubbleClockController(res, layoutInflater, colorExtractor));
Robert Snoeberger7fd7f512019-04-12 13:46:16 -0400152 addBuiltinClock(() -> new AnalogClockController(res, layoutInflater, colorExtractor));
Robert Snoeberger71e50792019-02-15 15:48:01 -0500153
154 // Store the size of the display for generation of clock preview.
155 DisplayMetrics dm = res.getDisplayMetrics();
156 mWidth = dm.widthPixels;
157 mHeight = dm.heightPixels;
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500158 }
159
160 /**
161 * Add listener to be notified when clock implementation should change.
162 */
163 public void addOnClockChangedListener(ClockChangedListener listener) {
164 if (mListeners.isEmpty()) {
165 register();
166 }
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400167 AvailableClocks availableClocks = new AvailableClocks();
168 for (int i = 0; i < mBuiltinClocks.size(); i++) {
169 availableClocks.addClockPlugin(mBuiltinClocks.get(i).get());
170 }
171 mListeners.put(listener, availableClocks);
172 mPluginManager.addPluginListener(availableClocks, ClockPlugin.class, true);
Robert Snoeberger71e50792019-02-15 15:48:01 -0500173 reload();
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500174 }
175
176 /**
177 * Remove listener added with {@link addOnClockChangedListener}.
178 */
179 public void removeOnClockChangedListener(ClockChangedListener listener) {
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400180 AvailableClocks availableClocks = mListeners.remove(listener);
181 mPluginManager.removePluginListener(availableClocks);
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500182 if (mListeners.isEmpty()) {
183 unregister();
184 }
185 }
186
Robert Snoeberger6b244b02019-02-04 15:33:31 -0500187 /**
188 * Get information about available clock faces.
189 */
190 List<ClockInfo> getClockInfos() {
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400191 return mPreviewClocks.getInfo();
Robert Snoeberger6b244b02019-02-04 15:33:31 -0500192 }
193
Robert Snoeberger71e50792019-02-15 15:48:01 -0500194 /**
195 * Get the current clock.
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400196 * @return current custom clock or null for default.
Robert Snoeberger71e50792019-02-15 15:48:01 -0500197 */
198 @Nullable
199 ClockPlugin getCurrentClock() {
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400200 return mPreviewClocks.getCurrentClock();
Robert Snoeberger71e50792019-02-15 15:48:01 -0500201 }
202
203 @VisibleForTesting
204 boolean isDocked() {
205 return mIsDocked;
206 }
207
208 @VisibleForTesting
209 ContentObserver getContentObserver() {
210 return mContentObserver;
211 }
212
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400213 private void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
214 ClockPlugin plugin = pluginSupplier.get();
215 mPreviewClocks.addClockPlugin(plugin);
216 mBuiltinClocks.add(pluginSupplier);
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500217 }
218
219 private void register() {
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400220 mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true);
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500221 mContentResolver.registerContentObserver(
222 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
Robert Snoeberger372e13f2019-04-08 17:07:14 -0400223 false, mContentObserver, UserHandle.USER_ALL);
Robert Snoebergerb300a4e2019-02-13 20:13:53 +0000224 mContentResolver.registerContentObserver(
225 Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
Robert Snoeberger372e13f2019-04-08 17:07:14 -0400226 false, mContentObserver, UserHandle.USER_ALL);
Robert Snoebergerbe2a1882019-04-10 13:48:02 -0400227 mCurrentUserObservable.getCurrentUser().observeForever(mCurrentUserObserver);
Robert Snoebergerb300a4e2019-02-13 20:13:53 +0000228 if (mDockManager != null) {
229 mDockManager.addListener(mDockEventListener);
230 }
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500231 }
232
233 private void unregister() {
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400234 mPluginManager.removePluginListener(mPreviewClocks);
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500235 mContentResolver.unregisterContentObserver(mContentObserver);
Robert Snoebergerbe2a1882019-04-10 13:48:02 -0400236 mCurrentUserObservable.getCurrentUser().removeObserver(mCurrentUserObserver);
Robert Snoebergerb300a4e2019-02-13 20:13:53 +0000237 if (mDockManager != null) {
238 mDockManager.removeListener(mDockEventListener);
239 }
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500240 }
241
Robert Snoeberger71e50792019-02-15 15:48:01 -0500242 private void reload() {
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400243 mPreviewClocks.reload();
244 mListeners.forEach((listener, clocks) -> {
245 clocks.reload();
246 ClockPlugin clock = clocks.getCurrentClock();
247 if (clock instanceof DefaultClockController) {
248 listener.onClockChanged(null);
249 } else {
250 listener.onClockChanged(clock);
Robert Snoeberger71e50792019-02-15 15:48:01 -0500251 }
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400252 });
Robert Snoebergerb300a4e2019-02-13 20:13:53 +0000253 }
254
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500255 /**
256 * Listener for events that should cause the custom clock face to change.
257 */
258 public interface ClockChangedListener {
259 /**
260 * Called when custom clock should change.
261 *
262 * @param clock Custom clock face to use. A null value indicates the default clock face.
263 */
264 void onClockChanged(ClockPlugin clock);
265 }
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400266
267 /**
268 * Collection of available clocks.
269 */
270 private final class AvailableClocks implements PluginListener<ClockPlugin> {
271
272 /**
273 * Map from expected value stored in settings to plugin for custom clock face.
274 */
275 private final Map<String, ClockPlugin> mClocks = new ArrayMap<>();
276
277 /**
278 * Metadata about available clocks, such as name and preview images.
279 */
280 private final List<ClockInfo> mClockInfo = new ArrayList<>();
281
282 /**
283 * Active ClockPlugin.
284 */
285 @Nullable private ClockPlugin mCurrentClock;
286
287 @Override
288 public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
289 addClockPlugin(plugin);
290 reload();
shawnlin366d1262019-04-24 14:16:19 +0800291 if (plugin == mCurrentClock) {
292 ClockManager.this.reload();
293 }
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400294 }
295
296 @Override
297 public void onPluginDisconnected(ClockPlugin plugin) {
shawnlin366d1262019-04-24 14:16:19 +0800298 boolean isCurrentClock = plugin == mCurrentClock;
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400299 removeClockPlugin(plugin);
300 reload();
shawnlin366d1262019-04-24 14:16:19 +0800301 if (isCurrentClock) {
302 ClockManager.this.reload();
303 }
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400304 }
305
306 /**
307 * Get the current clock.
308 * @return current custom clock or null for default.
309 */
310 @Nullable
311 ClockPlugin getCurrentClock() {
312 return mCurrentClock;
313 }
314
315 /**
316 * Get information about available clock faces.
317 */
318 List<ClockInfo> getInfo() {
319 return mClockInfo;
320 }
321
322 /**
323 * Adds a clock plugin to the collection of available clocks.
324 *
325 * @param plugin The plugin to add.
326 */
327 void addClockPlugin(ClockPlugin plugin) {
328 final String id = plugin.getClass().getName();
329 mClocks.put(plugin.getClass().getName(), plugin);
330 mClockInfo.add(ClockInfo.builder()
331 .setName(plugin.getName())
332 .setTitle(plugin.getTitle())
333 .setId(id)
334 .setThumbnail(plugin::getThumbnail)
335 .setPreview(() -> plugin.getPreview(mWidth, mHeight))
336 .build());
337 }
338
339 private void removeClockPlugin(ClockPlugin plugin) {
340 final String id = plugin.getClass().getName();
341 mClocks.remove(id);
342 for (int i = 0; i < mClockInfo.size(); i++) {
343 if (id.equals(mClockInfo.get(i).getId())) {
344 mClockInfo.remove(i);
345 break;
346 }
347 }
348 }
349
350 /**
351 * Update the current clock.
352 */
353 void reload() {
354 mCurrentClock = getClockPlugin();
355 }
356
357 private ClockPlugin getClockPlugin() {
358 ClockPlugin plugin = null;
359 if (ClockManager.this.isDocked()) {
Robert Snoeberger372e13f2019-04-08 17:07:14 -0400360 final String name = mSettingsWrapper.getDockedClockFace(
Robert Snoebergerbe2a1882019-04-10 13:48:02 -0400361 mCurrentUserObservable.getCurrentUser().getValue());
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400362 if (name != null) {
363 plugin = mClocks.get(name);
364 if (plugin != null) {
365 return plugin;
366 }
367 }
368 }
Robert Snoeberger372e13f2019-04-08 17:07:14 -0400369 final String name = mSettingsWrapper.getLockScreenCustomClockFace(
Robert Snoebergerbe2a1882019-04-10 13:48:02 -0400370 mCurrentUserObservable.getCurrentUser().getValue());
Robert Snoebergerd8445ea2019-03-27 10:41:23 -0400371 if (name != null) {
372 plugin = mClocks.get(name);
373 }
374 return plugin;
375 }
376 }
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500377}