• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package com.android.keyguard.clock;
17 
18 import android.annotation.Nullable;
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.database.ContentObserver;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.UserHandle;
27 import android.provider.Settings;
28 import android.util.ArrayMap;
29 import android.util.DisplayMetrics;
30 import android.view.LayoutInflater;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.systemui.colorextraction.SysuiColorExtractor;
36 import com.android.systemui.dagger.SysUISingleton;
37 import com.android.systemui.dagger.qualifiers.Main;
38 import com.android.systemui.dock.DockManager;
39 import com.android.systemui.dock.DockManager.DockEventListener;
40 import com.android.systemui.plugins.ClockPlugin;
41 import com.android.systemui.plugins.PluginListener;
42 import com.android.systemui.plugins.PluginManager;
43 import com.android.systemui.settings.UserTracker;
44 
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Objects;
50 import java.util.concurrent.Executor;
51 import java.util.function.Supplier;
52 
53 import javax.inject.Inject;
54 
55 /**
56  * Manages custom clock faces for AOD and lock screen.
57  *
58  * @deprecated Migrate to ClockRegistry
59  */
60 @SysUISingleton
61 @Deprecated
62 public final class ClockManager {
63 
64     private static final String TAG = "ClockOptsProvider";
65 
66     private final AvailableClocks mPreviewClocks;
67     private final List<Supplier<ClockPlugin>> mBuiltinClocks = new ArrayList<>();
68 
69     private final Context mContext;
70     private final ContentResolver mContentResolver;
71     private final SettingsWrapper mSettingsWrapper;
72     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
73     private final UserTracker mUserTracker;
74     private final Executor mMainExecutor;
75 
76     /**
77      * Observe settings changes to know when to switch the clock face.
78      */
79     private final ContentObserver mContentObserver =
80             new ContentObserver(mMainHandler) {
81                 @Override
82                 public void onChange(boolean selfChange, Collection<Uri> uris,
83                         int flags, int userId) {
84                     if (Objects.equals(userId,
85                             mUserTracker.getUserId())) {
86                         reload();
87                     }
88                 }
89             };
90 
91     /**
92      * Observe user changes and react by potentially loading the custom clock for the new user.
93      */
94     private final UserTracker.Callback mUserChangedCallback =
95             new UserTracker.Callback() {
96                 @Override
97                 public void onUserChanged(int newUser, @NonNull Context userContext) {
98                     reload();
99                 }
100             };
101 
102     private final PluginManager mPluginManager;
103     @Nullable private final DockManager mDockManager;
104 
105     /**
106      * Observe changes to dock state to know when to switch the clock face.
107      */
108     private final DockEventListener mDockEventListener =
109             new DockEventListener() {
110                 @Override
111                 public void onEvent(int event) {
112                     mIsDocked = (event == DockManager.STATE_DOCKED
113                             || event == DockManager.STATE_DOCKED_HIDE);
114                     reload();
115                 }
116             };
117 
118     /**
119      * When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
120      * to show.
121      */
122     private boolean mIsDocked;
123 
124     /**
125      * Listeners for onClockChanged event.
126      *
127      * Each listener must receive a separate clock plugin instance. Otherwise, there could be
128      * problems like attempting to attach a view that already has a parent. To deal with this issue,
129      * each listener is associated with a collection of available clocks. When onClockChanged is
130      * fired the current clock plugin instance is retrieved from that listeners available clocks.
131      */
132     private final Map<ClockChangedListener, AvailableClocks> mListeners = new ArrayMap<>();
133 
134     private final int mWidth;
135     private final int mHeight;
136 
137     @Inject
ClockManager(Context context, LayoutInflater layoutInflater, PluginManager pluginManager, SysuiColorExtractor colorExtractor, @Nullable DockManager dockManager, UserTracker userTracker, @Main Executor mainExecutor)138     public ClockManager(Context context, LayoutInflater layoutInflater,
139             PluginManager pluginManager, SysuiColorExtractor colorExtractor,
140             @Nullable DockManager dockManager, UserTracker userTracker,
141             @Main Executor mainExecutor) {
142         this(context, layoutInflater, pluginManager, colorExtractor,
143                 context.getContentResolver(), userTracker, mainExecutor,
144                 new SettingsWrapper(context.getContentResolver()), dockManager);
145     }
146 
147     @VisibleForTesting
ClockManager(Context context, LayoutInflater layoutInflater, PluginManager pluginManager, SysuiColorExtractor colorExtractor, ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor, SettingsWrapper settingsWrapper, DockManager dockManager)148     ClockManager(Context context, LayoutInflater layoutInflater,
149             PluginManager pluginManager, SysuiColorExtractor colorExtractor,
150             ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
151             SettingsWrapper settingsWrapper, DockManager dockManager) {
152         mContext = context;
153         mPluginManager = pluginManager;
154         mContentResolver = contentResolver;
155         mSettingsWrapper = settingsWrapper;
156         mUserTracker = userTracker;
157         mMainExecutor = mainExecutor;
158         mDockManager = dockManager;
159         mPreviewClocks = new AvailableClocks();
160 
161         Resources res = context.getResources();
162 
163         addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
164 
165         // Store the size of the display for generation of clock preview.
166         DisplayMetrics dm = res.getDisplayMetrics();
167         mWidth = dm.widthPixels;
168         mHeight = dm.heightPixels;
169     }
170 
171     /**
172      * Add listener to be notified when clock implementation should change.
173      */
addOnClockChangedListener(ClockChangedListener listener)174     public void addOnClockChangedListener(ClockChangedListener listener) {
175         if (mListeners.isEmpty()) {
176             register();
177         }
178         AvailableClocks availableClocks = new AvailableClocks();
179         for (int i = 0; i < mBuiltinClocks.size(); i++) {
180             availableClocks.addClockPlugin(mBuiltinClocks.get(i).get());
181         }
182         mListeners.put(listener, availableClocks);
183         mPluginManager.addPluginListener(availableClocks, ClockPlugin.class, true);
184         reload();
185     }
186 
187     /**
188      * Remove listener added with {@link addOnClockChangedListener}.
189      */
removeOnClockChangedListener(ClockChangedListener listener)190     public void removeOnClockChangedListener(ClockChangedListener listener) {
191         AvailableClocks availableClocks = mListeners.remove(listener);
192         mPluginManager.removePluginListener(availableClocks);
193         if (mListeners.isEmpty()) {
194             unregister();
195         }
196     }
197 
198     /**
199      * Get information about available clock faces.
200      */
getClockInfos()201     List<ClockInfo> getClockInfos() {
202         return mPreviewClocks.getInfo();
203     }
204 
205     /**
206      * Get the current clock.
207      * @return current custom clock or null for default.
208      */
209     @Nullable
getCurrentClock()210     ClockPlugin getCurrentClock() {
211         return mPreviewClocks.getCurrentClock();
212     }
213 
214     @VisibleForTesting
isDocked()215     boolean isDocked() {
216         return mIsDocked;
217     }
218 
219     @VisibleForTesting
getContentObserver()220     ContentObserver getContentObserver() {
221         return mContentObserver;
222     }
223 
224     @VisibleForTesting
addBuiltinClock(Supplier<ClockPlugin> pluginSupplier)225     void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
226         ClockPlugin plugin = pluginSupplier.get();
227         mPreviewClocks.addClockPlugin(plugin);
228         mBuiltinClocks.add(pluginSupplier);
229     }
230 
register()231     private void register() {
232         mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true);
233         mContentResolver.registerContentObserver(
234                 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
235                 false, mContentObserver, UserHandle.USER_ALL);
236         mContentResolver.registerContentObserver(
237                 Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
238                 false, mContentObserver, UserHandle.USER_ALL);
239         mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
240         if (mDockManager != null) {
241             mDockManager.addListener(mDockEventListener);
242         }
243     }
244 
unregister()245     private void unregister() {
246         mPluginManager.removePluginListener(mPreviewClocks);
247         mContentResolver.unregisterContentObserver(mContentObserver);
248         mUserTracker.removeCallback(mUserChangedCallback);
249         if (mDockManager != null) {
250             mDockManager.removeListener(mDockEventListener);
251         }
252     }
253 
reload()254     private void reload() {
255         mPreviewClocks.reloadCurrentClock();
256         mListeners.forEach((listener, clocks) -> {
257             clocks.reloadCurrentClock();
258             final ClockPlugin clock = clocks.getCurrentClock();
259             if (Looper.myLooper() == Looper.getMainLooper()) {
260                 listener.onClockChanged(clock instanceof DefaultClockController ? null : clock);
261             } else {
262                 mMainHandler.post(() -> listener.onClockChanged(
263                         clock instanceof DefaultClockController ? null : clock));
264             }
265         });
266     }
267 
268     /**
269      * Listener for events that should cause the custom clock face to change.
270      */
271     public interface ClockChangedListener {
272         /**
273          * Called when custom clock should change.
274          *
275          * @param clock Custom clock face to use. A null value indicates the default clock face.
276          */
onClockChanged(ClockPlugin clock)277         void onClockChanged(ClockPlugin clock);
278     }
279 
280     /**
281      * Collection of available clocks.
282      */
283     private final class AvailableClocks implements PluginListener<ClockPlugin> {
284 
285         /**
286          * Map from expected value stored in settings to plugin for custom clock face.
287          */
288         private final Map<String, ClockPlugin> mClocks = new ArrayMap<>();
289 
290         /**
291          * Metadata about available clocks, such as name and preview images.
292          */
293         private final List<ClockInfo> mClockInfo = new ArrayList<>();
294 
295         /**
296          * Active ClockPlugin.
297          */
298         @Nullable private ClockPlugin mCurrentClock;
299 
300         @Override
onPluginConnected(ClockPlugin plugin, Context pluginContext)301         public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
302             addClockPlugin(plugin);
303             reloadIfNeeded(plugin);
304         }
305 
306         @Override
onPluginDisconnected(ClockPlugin plugin)307         public void onPluginDisconnected(ClockPlugin plugin) {
308             removeClockPlugin(plugin);
309             reloadIfNeeded(plugin);
310         }
311 
312         /**
313          * Get the current clock.
314          * @return current custom clock or null for default.
315          */
316         @Nullable
getCurrentClock()317         ClockPlugin getCurrentClock() {
318             return mCurrentClock;
319         }
320 
321         /**
322          * Get information about available clock faces.
323          */
getInfo()324         List<ClockInfo> getInfo() {
325             return mClockInfo;
326         }
327 
328         /**
329          * Adds a clock plugin to the collection of available clocks.
330          *
331          * @param plugin The plugin to add.
332          */
addClockPlugin(ClockPlugin plugin)333         void addClockPlugin(ClockPlugin plugin) {
334             final String id = plugin.getClass().getName();
335             mClocks.put(plugin.getClass().getName(), plugin);
336             mClockInfo.add(ClockInfo.builder()
337                     .setName(plugin.getName())
338                     .setTitle(plugin::getTitle)
339                     .setId(id)
340                     .setThumbnail(plugin::getThumbnail)
341                     .setPreview(() -> plugin.getPreview(mWidth, mHeight))
342                     .build());
343         }
344 
removeClockPlugin(ClockPlugin plugin)345         private void removeClockPlugin(ClockPlugin plugin) {
346             final String id = plugin.getClass().getName();
347             mClocks.remove(id);
348             for (int i = 0; i < mClockInfo.size(); i++) {
349                 if (id.equals(mClockInfo.get(i).getId())) {
350                     mClockInfo.remove(i);
351                     break;
352                 }
353             }
354         }
355 
reloadIfNeeded(ClockPlugin plugin)356         private void reloadIfNeeded(ClockPlugin plugin) {
357             final boolean wasCurrentClock = plugin == mCurrentClock;
358             reloadCurrentClock();
359             final boolean isCurrentClock = plugin == mCurrentClock;
360             if (wasCurrentClock || isCurrentClock) {
361                 ClockManager.this.reload();
362             }
363         }
364 
365         /**
366          * Update the current clock.
367          */
reloadCurrentClock()368         void reloadCurrentClock() {
369             mCurrentClock = getClockPlugin();
370         }
371 
getClockPlugin()372         private ClockPlugin getClockPlugin() {
373             ClockPlugin plugin = null;
374             if (ClockManager.this.isDocked()) {
375                 final String name = mSettingsWrapper.getDockedClockFace(
376                         mUserTracker.getUserId());
377                 if (name != null) {
378                     plugin = mClocks.get(name);
379                     if (plugin != null) {
380                         return plugin;
381                     }
382                 }
383             }
384             final String name = mSettingsWrapper.getLockScreenCustomClockFace(
385                     mUserTracker.getUserId());
386             if (name != null) {
387                 plugin = mClocks.get(name);
388             }
389             return plugin;
390         }
391     }
392 }
393