1 /*
2  * Copyright (C) 2020 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 
17 package com.android.keyguard;
18 
19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
21 
22 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
23 
24 import android.app.WallpaperManager;
25 import android.content.res.Resources;
26 import android.database.ContentObserver;
27 import android.provider.Settings;
28 import android.text.TextUtils;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.FrameLayout;
32 import android.widget.LinearLayout;
33 import android.widget.RelativeLayout;
34 
35 import com.android.internal.colorextraction.ColorExtractor;
36 import com.android.keyguard.clock.ClockManager;
37 import com.android.systemui.R;
38 import com.android.systemui.broadcast.BroadcastDispatcher;
39 import com.android.systemui.colorextraction.SysuiColorExtractor;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
42 import com.android.systemui.plugins.ClockPlugin;
43 import com.android.systemui.plugins.statusbar.StatusBarStateController;
44 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
45 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
46 import com.android.systemui.statusbar.notification.AnimatableProperty;
47 import com.android.systemui.statusbar.notification.PropertyAnimator;
48 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
49 import com.android.systemui.statusbar.phone.KeyguardBypassController;
50 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
51 import com.android.systemui.statusbar.phone.NotificationIconContainer;
52 import com.android.systemui.statusbar.policy.BatteryController;
53 import com.android.systemui.util.ViewController;
54 import com.android.systemui.util.settings.SecureSettings;
55 
56 import java.util.HashSet;
57 import java.util.Locale;
58 import java.util.Set;
59 import java.util.TimeZone;
60 import java.util.concurrent.Executor;
61 
62 import javax.inject.Inject;
63 
64 /**
65  * Injectable controller for {@link KeyguardClockSwitch}.
66  */
67 public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch> {
68     private static final boolean CUSTOM_CLOCKS_ENABLED = true;
69 
70     private final StatusBarStateController mStatusBarStateController;
71     private final SysuiColorExtractor mColorExtractor;
72     private final ClockManager mClockManager;
73     private final KeyguardSliceViewController mKeyguardSliceViewController;
74     private final NotificationIconAreaController mNotificationIconAreaController;
75     private final BroadcastDispatcher mBroadcastDispatcher;
76     private final BatteryController mBatteryController;
77     private final LockscreenSmartspaceController mSmartspaceController;
78     private final Resources mResources;
79     private final SecureSettings mSecureSettings;
80 
81     /**
82      * Clock for both small and large sizes
83      */
84     private AnimatableClockController mClockViewController;
85     private FrameLayout mClockFrame; // top aligned clock
86     private AnimatableClockController mLargeClockViewController;
87     private FrameLayout mLargeClockFrame; // centered clock
88 
89     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
90     private final KeyguardBypassController mBypassController;
91 
92     private int mKeyguardClockTopMargin = 0;
93 
94     /**
95      * Listener for changes to the color palette.
96      *
97      * The color palette changes when the wallpaper is changed.
98      */
99     private final ColorExtractor.OnColorsChangedListener mColorsListener =
100             (extractor, which) -> {
101                 if ((which & WallpaperManager.FLAG_LOCK) != 0) {
102                     mView.updateColors(getGradientColors());
103                 }
104             };
105 
106     private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
107 
108     private ViewGroup mStatusArea;
109     // If set will replace keyguard_slice_view
110     private View mSmartspaceView;
111 
112     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
113     private SmartspaceTransitionController mSmartspaceTransitionController;
114 
115     private boolean mOnlyClock = false;
116     private Executor mUiExecutor;
117     private boolean mCanShowDoubleLineClock = true;
118     private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
119         @Override
120         public void onChange(boolean change) {
121             updateDoubleLineClock();
122         }
123     };
124 
125     @Inject
KeyguardClockSwitchController( KeyguardClockSwitch keyguardClockSwitch, StatusBarStateController statusBarStateController, SysuiColorExtractor colorExtractor, ClockManager clockManager, KeyguardSliceViewController keyguardSliceViewController, NotificationIconAreaController notificationIconAreaController, BroadcastDispatcher broadcastDispatcher, BatteryController batteryController, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardBypassController bypassController, LockscreenSmartspaceController smartspaceController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, SmartspaceTransitionController smartspaceTransitionController, SecureSettings secureSettings, @Main Executor uiExecutor, @Main Resources resources)126     public KeyguardClockSwitchController(
127             KeyguardClockSwitch keyguardClockSwitch,
128             StatusBarStateController statusBarStateController,
129             SysuiColorExtractor colorExtractor,
130             ClockManager clockManager,
131             KeyguardSliceViewController keyguardSliceViewController,
132             NotificationIconAreaController notificationIconAreaController,
133             BroadcastDispatcher broadcastDispatcher,
134             BatteryController batteryController,
135             KeyguardUpdateMonitor keyguardUpdateMonitor,
136             KeyguardBypassController bypassController,
137             LockscreenSmartspaceController smartspaceController,
138             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
139             SmartspaceTransitionController smartspaceTransitionController,
140             SecureSettings secureSettings,
141             @Main Executor uiExecutor,
142             @Main Resources resources) {
143         super(keyguardClockSwitch);
144         mStatusBarStateController = statusBarStateController;
145         mColorExtractor = colorExtractor;
146         mClockManager = clockManager;
147         mKeyguardSliceViewController = keyguardSliceViewController;
148         mNotificationIconAreaController = notificationIconAreaController;
149         mBroadcastDispatcher = broadcastDispatcher;
150         mBatteryController = batteryController;
151         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
152         mBypassController = bypassController;
153         mSmartspaceController = smartspaceController;
154         mResources = resources;
155         mSecureSettings = secureSettings;
156         mUiExecutor = uiExecutor;
157         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
158         mSmartspaceTransitionController = smartspaceTransitionController;
159     }
160 
161     /**
162      * Mostly used for alternate displays, limit the information shown
163      */
setOnlyClock(boolean onlyClock)164     public void setOnlyClock(boolean onlyClock) {
165         mOnlyClock = onlyClock;
166     }
167 
168     /**
169      * Attach the controller to the view it relates to.
170      */
171     @Override
onInit()172     public void onInit() {
173         mKeyguardSliceViewController.init();
174 
175         mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
176         mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
177 
178         mClockViewController =
179                 new AnimatableClockController(
180                         mView.findViewById(R.id.animatable_clock_view),
181                         mStatusBarStateController,
182                         mBroadcastDispatcher,
183                         mBatteryController,
184                         mKeyguardUpdateMonitor,
185                         mResources);
186         mClockViewController.init();
187 
188         mLargeClockViewController =
189                 new AnimatableClockController(
190                         mView.findViewById(R.id.animatable_clock_view_large),
191                         mStatusBarStateController,
192                         mBroadcastDispatcher,
193                         mBatteryController,
194                         mKeyguardUpdateMonitor,
195                         mResources);
196         mLargeClockViewController.init();
197     }
198 
199     @Override
onViewAttached()200     protected void onViewAttached() {
201         if (CUSTOM_CLOCKS_ENABLED) {
202             mClockManager.addOnClockChangedListener(mClockChangedListener);
203         }
204         mColorExtractor.addOnColorsChangedListener(mColorsListener);
205         mView.updateColors(getGradientColors());
206         mKeyguardClockTopMargin =
207                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
208 
209         if (mOnlyClock) {
210             View ksv = mView.findViewById(R.id.keyguard_slice_view);
211             ksv.setVisibility(View.GONE);
212 
213             View nic = mView.findViewById(
214                     R.id.left_aligned_notification_icon_container);
215             nic.setVisibility(View.GONE);
216             return;
217         }
218         updateAodIcons();
219 
220         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
221 
222         if (mSmartspaceController.isEnabled()) {
223             mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
224             View ksv = mView.findViewById(R.id.keyguard_slice_view);
225             int ksvIndex = mStatusArea.indexOfChild(ksv);
226             ksv.setVisibility(View.GONE);
227 
228             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
229                     MATCH_PARENT, WRAP_CONTENT);
230 
231             mStatusArea.addView(mSmartspaceView, ksvIndex, lp);
232             int startPadding = getContext().getResources()
233                     .getDimensionPixelSize(R.dimen.below_clock_padding_start);
234             int endPadding = getContext().getResources()
235                     .getDimensionPixelSize(R.dimen.below_clock_padding_end);
236             mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
237 
238             updateClockLayout();
239             mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
240         }
241 
242         mSecureSettings.registerContentObserver(
243                 Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
244                 false, /* notifyForDescendants */
245                 mDoubleLineClockObserver
246         );
247 
248         updateDoubleLineClock();
249     }
250 
getNotificationIconAreaHeight()251     int getNotificationIconAreaHeight() {
252         return mNotificationIconAreaController.getHeight();
253     }
254 
255     @Override
onViewDetached()256     protected void onViewDetached() {
257         if (CUSTOM_CLOCKS_ENABLED) {
258             mClockManager.removeOnClockChangedListener(mClockChangedListener);
259         }
260         mColorExtractor.removeOnColorsChangedListener(mColorsListener);
261         mView.setClockPlugin(null, mStatusBarStateController.getState());
262 
263         mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
264     }
265 
266     /**
267      * Apply dp changes on font/scale change
268      */
onDensityOrFontScaleChanged()269     public void onDensityOrFontScaleChanged() {
270         mView.onDensityOrFontScaleChanged();
271         mKeyguardClockTopMargin =
272                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
273 
274         updateClockLayout();
275     }
276 
updateClockLayout()277     private void updateClockLayout() {
278         int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
279                 R.dimen.keyguard_large_clock_top_margin);
280 
281         RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
282                 MATCH_PARENT);
283         lp.topMargin = largeClockTopMargin;
284         mLargeClockFrame.setLayoutParams(lp);
285     }
286 
287     /**
288      * Set which clock should be displayed on the keyguard. The other one will be automatically
289      * hidden.
290      */
displayClock(@eyguardClockSwitch.ClockSize int clockSize)291     public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
292         if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
293             return;
294         }
295 
296         boolean appeared = mView.switchToClock(clockSize);
297         if (appeared && clockSize == LARGE) {
298             mLargeClockViewController.animateAppear();
299         }
300     }
301 
302     /**
303      * If we're presenting a custom clock of just the default one.
304      */
hasCustomClock()305     public boolean hasCustomClock() {
306         return mView.hasCustomClock();
307     }
308 
309     /**
310      * Get the clock text size.
311      */
getClockTextSize()312     public float getClockTextSize() {
313         return mView.getTextSize();
314     }
315 
316     /**
317      * Refresh clock. Called in response to TIME_TICK broadcasts.
318      */
refresh()319     void refresh() {
320         if (mClockViewController != null) {
321             mClockViewController.refreshTime();
322             mLargeClockViewController.refreshTime();
323         }
324         if (mSmartspaceController != null) {
325             mSmartspaceController.requestSmartspaceUpdate();
326         }
327 
328         mView.refresh();
329     }
330 
331     /**
332      * Update position of the view, with optional animation. Move the slice view and the clock
333      * slightly towards the center in order to prevent burn-in. Y positioning occurs at the
334      * view parent level. The large clock view will scale instead of using x position offsets, to
335      * keep the clock centered.
336      */
updatePosition(int x, float scale, AnimationProperties props, boolean animate)337     void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
338         x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
339 
340         PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
341                 x, props, animate);
342         PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
343                 scale, props, animate);
344         PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
345                 scale, props, animate);
346 
347         if (mStatusArea != null) {
348             PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
349                     x, props, animate);
350 
351             // If we're unlocking with the SmartSpace shared element transition, let the controller
352             // know that it should re-position our SmartSpace.
353             if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) {
354                 mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition();
355             }
356         }
357     }
358 
359     /** Sets an alpha value on every child view except for the smartspace. */
setChildrenAlphaExcludingSmartspace(float alpha)360     public void setChildrenAlphaExcludingSmartspace(float alpha) {
361         final Set<View> excludedViews = new HashSet<>();
362 
363         if (mSmartspaceView != null) {
364             excludedViews.add(mSmartspaceView);
365         }
366 
367         setChildrenAlphaExcluding(alpha, excludedViews);
368     }
369 
370     /** Sets an alpha value on every child view except for the views in the provided set. */
setChildrenAlphaExcluding(float alpha, Set<View> excludedViews)371     public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) {
372         for (int i = 0; i < mView.getChildCount(); i++) {
373             final View child = mView.getChildAt(i);
374 
375             if (!excludedViews.contains(child)) {
376                 child.setAlpha(alpha);
377             }
378         }
379     }
380 
updateTimeZone(TimeZone timeZone)381     void updateTimeZone(TimeZone timeZone) {
382         mView.onTimeZoneChanged(timeZone);
383         if (mClockViewController != null) {
384             mClockViewController.onTimeZoneChanged(timeZone);
385             mLargeClockViewController.onTimeZoneChanged(timeZone);
386         }
387     }
388 
refreshFormat()389     void refreshFormat() {
390         if (mClockViewController != null) {
391             mClockViewController.refreshFormat();
392             mLargeClockViewController.refreshFormat();
393         }
394     }
395 
396     /**
397      * Get y-bottom position of the currently visible clock on the keyguard.
398      * We can't directly getBottom() because clock changes positions in AOD for burn-in
399      */
getClockBottom(int statusBarHeaderHeight)400     int getClockBottom(int statusBarHeaderHeight) {
401         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
402             View clock = mLargeClockFrame.findViewById(
403                     com.android.systemui.R.id.animatable_clock_view_large);
404             int frameHeight = mLargeClockFrame.getHeight();
405             int clockHeight = clock.getHeight();
406             return frameHeight / 2 + clockHeight / 2;
407         } else {
408             return mClockFrame.findViewById(
409                     com.android.systemui.R.id.animatable_clock_view).getHeight()
410                     + statusBarHeaderHeight + mKeyguardClockTopMargin;
411         }
412     }
413 
isClockTopAligned()414     boolean isClockTopAligned() {
415         return mLargeClockFrame.getVisibility() != View.VISIBLE;
416     }
417 
updateAodIcons()418     private void updateAodIcons() {
419         NotificationIconContainer nic = (NotificationIconContainer)
420                 mView.findViewById(
421                         com.android.systemui.R.id.left_aligned_notification_icon_container);
422         mNotificationIconAreaController.setupAodIcons(nic);
423     }
424 
setClockPlugin(ClockPlugin plugin)425     private void setClockPlugin(ClockPlugin plugin) {
426         mView.setClockPlugin(plugin, mStatusBarStateController.getState());
427     }
428 
getGradientColors()429     private ColorExtractor.GradientColors getGradientColors() {
430         return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
431     }
432 
getCurrentLayoutDirection()433     private int getCurrentLayoutDirection() {
434         return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
435     }
436 
updateDoubleLineClock()437     private void updateDoubleLineClock() {
438         mCanShowDoubleLineClock = mSecureSettings.getInt(
439             Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) != 0;
440 
441         if (!mCanShowDoubleLineClock) {
442             mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL));
443         }
444     }
445 }
446