• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
24 
25 import android.annotation.Nullable;
26 import android.database.ContentObserver;
27 import android.os.UserHandle;
28 import android.provider.Settings;
29 import android.text.TextUtils;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.FrameLayout;
33 import android.widget.LinearLayout;
34 
35 import androidx.annotation.NonNull;
36 
37 import com.android.systemui.Dumpable;
38 import com.android.systemui.R;
39 import com.android.systemui.dagger.qualifiers.Main;
40 import com.android.systemui.dump.DumpManager;
41 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
42 import com.android.systemui.log.dagger.KeyguardClockLog;
43 import com.android.systemui.plugins.ClockAnimations;
44 import com.android.systemui.plugins.ClockController;
45 import com.android.systemui.plugins.log.LogBuffer;
46 import com.android.systemui.plugins.log.LogLevel;
47 import com.android.systemui.plugins.statusbar.StatusBarStateController;
48 import com.android.systemui.shared.clocks.ClockRegistry;
49 import com.android.systemui.shared.regionsampling.RegionSampler;
50 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
51 import com.android.systemui.statusbar.notification.AnimatableProperty;
52 import com.android.systemui.statusbar.notification.PropertyAnimator;
53 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
54 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
55 import com.android.systemui.statusbar.phone.NotificationIconContainer;
56 import com.android.systemui.util.ViewController;
57 import com.android.systemui.util.settings.SecureSettings;
58 
59 import java.io.PrintWriter;
60 import java.util.Locale;
61 import java.util.concurrent.Executor;
62 
63 import javax.inject.Inject;
64 
65 /**
66  * Injectable controller for {@link KeyguardClockSwitch}.
67  */
68 public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
69         implements Dumpable {
70     private static final String TAG = "KeyguardClockSwitchController";
71 
72     private final StatusBarStateController mStatusBarStateController;
73     private final ClockRegistry mClockRegistry;
74     private final KeyguardSliceViewController mKeyguardSliceViewController;
75     private final NotificationIconAreaController mNotificationIconAreaController;
76     private final LockscreenSmartspaceController mSmartspaceController;
77     private final SecureSettings mSecureSettings;
78     private final DumpManager mDumpManager;
79     private final ClockEventController mClockEventController;
80     private final LogBuffer mLogBuffer;
81 
82     private FrameLayout mSmallClockFrame; // top aligned clock
83     private FrameLayout mLargeClockFrame; // centered clock
84 
85     @KeyguardClockSwitch.ClockSize
86     private int mCurrentClockSize = SMALL;
87 
88     private int mKeyguardSmallClockTopMargin = 0;
89     private int mKeyguardLargeClockTopMargin = 0;
90     private final ClockRegistry.ClockChangeListener mClockChangedListener;
91 
92     private ViewGroup mStatusArea;
93 
94     // If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
95     private ViewGroup mDateWeatherView;
96     private View mWeatherView;
97     private View mSmartspaceView;
98 
99     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
100 
101     private boolean mOnlyClock = false;
102     private final Executor mUiExecutor;
103     private boolean mCanShowDoubleLineClock = true;
104     private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
105         @Override
106         public void onChange(boolean change) {
107             updateDoubleLineClock();
108         }
109     };
110     private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
111         @Override
112         public void onChange(boolean change) {
113             setWeatherVisibility();
114         }
115     };
116 
117     private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
118             mKeyguardUnlockAnimationListener =
119             new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
120                 @Override
121                 public void onUnlockAnimationFinished() {
122                     // For performance reasons, reset this once the unlock animation ends.
123                     setClipChildrenForUnlock(true);
124                 }
125             };
126 
127     @Inject
KeyguardClockSwitchController( KeyguardClockSwitch keyguardClockSwitch, StatusBarStateController statusBarStateController, ClockRegistry clockRegistry, KeyguardSliceViewController keyguardSliceViewController, NotificationIconAreaController notificationIconAreaController, LockscreenSmartspaceController smartspaceController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, SecureSettings secureSettings, @Main Executor uiExecutor, DumpManager dumpManager, ClockEventController clockEventController, @KeyguardClockLog LogBuffer logBuffer)128     public KeyguardClockSwitchController(
129             KeyguardClockSwitch keyguardClockSwitch,
130             StatusBarStateController statusBarStateController,
131             ClockRegistry clockRegistry,
132             KeyguardSliceViewController keyguardSliceViewController,
133             NotificationIconAreaController notificationIconAreaController,
134             LockscreenSmartspaceController smartspaceController,
135             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
136             SecureSettings secureSettings,
137             @Main Executor uiExecutor,
138             DumpManager dumpManager,
139             ClockEventController clockEventController,
140             @KeyguardClockLog LogBuffer logBuffer) {
141         super(keyguardClockSwitch);
142         mStatusBarStateController = statusBarStateController;
143         mClockRegistry = clockRegistry;
144         mKeyguardSliceViewController = keyguardSliceViewController;
145         mNotificationIconAreaController = notificationIconAreaController;
146         mSmartspaceController = smartspaceController;
147         mSecureSettings = secureSettings;
148         mUiExecutor = uiExecutor;
149         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
150         mDumpManager = dumpManager;
151         mClockEventController = clockEventController;
152         mLogBuffer = logBuffer;
153         mView.setLogBuffer(mLogBuffer);
154 
155         mClockChangedListener = new ClockRegistry.ClockChangeListener() {
156             @Override
157             public void onCurrentClockChanged() {
158                 setClock(mClockRegistry.createCurrentClock());
159             }
160             @Override
161             public void onAvailableClocksChanged() { }
162         };
163     }
164 
165     /**
166      * Mostly used for alternate displays, limit the information shown
167      */
setOnlyClock(boolean onlyClock)168     public void setOnlyClock(boolean onlyClock) {
169         mOnlyClock = onlyClock;
170     }
171 
172     /**
173      * Attach the controller to the view it relates to.
174      */
175     @Override
onInit()176     protected void onInit() {
177         mKeyguardSliceViewController.init();
178 
179         mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
180         mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
181 
182         mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
183         mDumpManager.registerDumpable(getClass().toString(), this);
184     }
185 
186     @Override
onViewAttached()187     protected void onViewAttached() {
188         mClockRegistry.registerClockChangeListener(mClockChangedListener);
189         setClock(mClockRegistry.createCurrentClock());
190         mClockEventController.registerListeners(mView);
191         mKeyguardSmallClockTopMargin =
192                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
193         mKeyguardLargeClockTopMargin =
194                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
195 
196         if (mOnlyClock) {
197             View ksv = mView.findViewById(R.id.keyguard_slice_view);
198             ksv.setVisibility(View.GONE);
199 
200             View nic = mView.findViewById(
201                     R.id.left_aligned_notification_icon_container);
202             nic.setVisibility(View.GONE);
203             return;
204         }
205         updateAodIcons();
206 
207         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
208 
209         if (mSmartspaceController.isEnabled()) {
210             View ksv = mView.findViewById(R.id.keyguard_slice_view);
211             int viewIndex = mStatusArea.indexOfChild(ksv);
212             ksv.setVisibility(View.GONE);
213 
214             // TODO(b/261757708): add content observer for the Settings toggle and add/remove
215             //  weather according to the Settings.
216             if (mSmartspaceController.isDateWeatherDecoupled()) {
217                 addDateWeatherView(viewIndex);
218                 viewIndex += 1;
219             }
220 
221             addSmartspaceView(viewIndex);
222         }
223 
224         mSecureSettings.registerContentObserverForUser(
225                 Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
226                 false, /* notifyForDescendants */
227                 mDoubleLineClockObserver,
228                 UserHandle.USER_ALL
229         );
230 
231         mSecureSettings.registerContentObserverForUser(
232                 Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
233                 false, /* notifyForDescendants */
234                 mShowWeatherObserver,
235                 UserHandle.USER_ALL
236         );
237 
238         updateDoubleLineClock();
239         setWeatherVisibility();
240 
241         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
242                 mKeyguardUnlockAnimationListener);
243     }
244 
getNotificationIconAreaHeight()245     int getNotificationIconAreaHeight() {
246         return mNotificationIconAreaController.getHeight();
247     }
248 
249     @Override
onViewDetached()250     protected void onViewDetached() {
251         mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
252         mClockEventController.unregisterListeners();
253         setClock(null);
254 
255         mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
256 
257         mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
258                 mKeyguardUnlockAnimationListener);
259     }
260 
onLocaleListChanged()261     void onLocaleListChanged() {
262         if (mSmartspaceController.isEnabled()) {
263             if (mSmartspaceController.isDateWeatherDecoupled()) {
264                 mDateWeatherView.removeView(mWeatherView);
265                 int index = mStatusArea.indexOfChild(mDateWeatherView);
266                 if (index >= 0) {
267                     mStatusArea.removeView(mDateWeatherView);
268                     addDateWeatherView(index);
269                 }
270             }
271             int index = mStatusArea.indexOfChild(mSmartspaceView);
272             if (index >= 0) {
273                 mStatusArea.removeView(mSmartspaceView);
274                 addSmartspaceView(index);
275             }
276         }
277     }
278 
addDateWeatherView(int index)279     private void addDateWeatherView(int index) {
280         mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
281         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
282                 MATCH_PARENT, WRAP_CONTENT);
283         mStatusArea.addView(mDateWeatherView, index, lp);
284         int startPadding = getContext().getResources().getDimensionPixelSize(
285                 R.dimen.below_clock_padding_start);
286         int endPadding = getContext().getResources().getDimensionPixelSize(
287                 R.dimen.below_clock_padding_end);
288         mDateWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
289 
290         addWeatherView();
291     }
292 
addWeatherView()293     private void addWeatherView() {
294         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
295                 WRAP_CONTENT, WRAP_CONTENT);
296         mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
297         // Place weather right after the date, before the extras
298         final int index = mDateWeatherView.getChildCount() == 0 ? 0 : 1;
299         mDateWeatherView.addView(mWeatherView, index, lp);
300         mWeatherView.setPaddingRelative(0, 0, 4, 0);
301     }
302 
addSmartspaceView(int index)303     private void addSmartspaceView(int index) {
304         mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
305         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
306                 MATCH_PARENT, WRAP_CONTENT);
307         mStatusArea.addView(mSmartspaceView, index, lp);
308         int startPadding = getContext().getResources().getDimensionPixelSize(
309                 R.dimen.below_clock_padding_start);
310         int endPadding = getContext().getResources().getDimensionPixelSize(
311                 R.dimen.below_clock_padding_end);
312         mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
313 
314         mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
315     }
316 
317     /**
318      * Apply dp changes on font/scale change
319      */
onDensityOrFontScaleChanged()320     public void onDensityOrFontScaleChanged() {
321         mView.onDensityOrFontScaleChanged();
322         mKeyguardSmallClockTopMargin =
323                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
324         mKeyguardLargeClockTopMargin =
325                 mView.getResources().getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
326         mView.updateClockTargetRegions();
327     }
328 
329 
330     /**
331      * Set which clock should be displayed on the keyguard. The other one will be automatically
332      * hidden.
333      */
displayClock(@eyguardClockSwitch.ClockSize int clockSize, boolean animate)334     public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize, boolean animate) {
335         if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
336             return;
337         }
338 
339         mCurrentClockSize = clockSize;
340 
341         ClockController clock = getClock();
342         boolean appeared = mView.switchToClock(clockSize, animate);
343         if (clock != null && animate && appeared && clockSize == LARGE) {
344             clock.getAnimations().enter();
345         }
346     }
347 
348     /**
349      * Animates the clock view between folded and unfolded states
350      */
animateFoldToAod(float foldFraction)351     public void animateFoldToAod(float foldFraction) {
352         ClockController clock = getClock();
353         if (clock != null) {
354             clock.getAnimations().fold(foldFraction);
355         }
356     }
357 
358     /**
359      * Refresh clock. Called in response to TIME_TICK broadcasts.
360      */
refresh()361     void refresh() {
362         if (mSmartspaceController != null) {
363             mSmartspaceController.requestSmartspaceUpdate();
364         }
365         ClockController clock = getClock();
366         if (clock != null) {
367             clock.getSmallClock().getEvents().onTimeTick();
368             clock.getLargeClock().getEvents().onTimeTick();
369         }
370     }
371 
372     /**
373      * Update position of the view, with optional animation. Move the slice view and the clock
374      * slightly towards the center in order to prevent burn-in. Y positioning occurs at the
375      * view parent level. The large clock view will scale instead of using x position offsets, to
376      * keep the clock centered.
377      */
updatePosition(int x, float scale, AnimationProperties props, boolean animate)378     void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
379         x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
380 
381         PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
382                 x, props, animate);
383         PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
384                 scale, props, animate);
385         PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
386                 scale, props, animate);
387 
388         if (mStatusArea != null) {
389             PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
390                     x, props, animate);
391         }
392     }
393 
394     /**
395      * Get y-bottom position of the currently visible clock on the keyguard.
396      * We can't directly getBottom() because clock changes positions in AOD for burn-in
397      */
getClockBottom(int statusBarHeaderHeight)398     int getClockBottom(int statusBarHeaderHeight) {
399         ClockController clock = getClock();
400         if (clock == null) {
401             return 0;
402         }
403 
404         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
405             // This gets the expected clock bottom if mLargeClockFrame had a top margin, but it's
406             // top margin only contributed to height and didn't move the top of the view (as this
407             // was the computation previously). As we no longer have a margin, we add this back
408             // into the computation manually.
409             int frameHeight = mLargeClockFrame.getHeight();
410             int clockHeight = clock.getLargeClock().getView().getHeight();
411             return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
412         } else {
413             // This is only called if we've never shown the large clock as the frame is inflated
414             // with 'gone', but then the visibility is never set when it is animated away by
415             // KeyguardClockSwitch, instead it is removed from the view hierarchy.
416             // TODO(b/261755021): Cleanup Large Frame Visibility
417             int clockHeight = clock.getSmallClock().getView().getHeight();
418             return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
419         }
420     }
421 
422     /**
423      * Get the height of the currently visible clock on the keyguard.
424      */
getClockHeight()425     int getClockHeight() {
426         ClockController clock = getClock();
427         if (clock == null) {
428             return 0;
429         }
430 
431         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
432             return clock.getLargeClock().getView().getHeight();
433         } else {
434             // Is not called except in certain edge cases, see comment in getClockBottom
435             // TODO(b/261755021): Cleanup Large Frame Visibility
436             return clock.getSmallClock().getView().getHeight();
437         }
438     }
439 
isClockTopAligned()440     boolean isClockTopAligned() {
441         // Returns false except certain edge cases, see comment in getClockBottom
442         // TODO(b/261755021): Cleanup Large Frame Visibility
443         return mLargeClockFrame.getVisibility() != View.VISIBLE;
444     }
445 
updateAodIcons()446     private void updateAodIcons() {
447         NotificationIconContainer nic = (NotificationIconContainer)
448                 mView.findViewById(
449                         com.android.systemui.R.id.left_aligned_notification_icon_container);
450         mNotificationIconAreaController.setupAodIcons(nic);
451     }
452 
setClock(ClockController clock)453     private void setClock(ClockController clock) {
454         if (clock != null && mLogBuffer != null) {
455             mLogBuffer.log(TAG, LogLevel.INFO, "New Clock");
456         }
457 
458         mClockEventController.setClock(clock);
459         mView.setClock(clock, mStatusBarStateController.getState());
460     }
461 
462     @Nullable
getClock()463     private ClockController getClock() {
464         return mClockEventController.getClock();
465     }
466 
getCurrentLayoutDirection()467     private int getCurrentLayoutDirection() {
468         return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
469     }
470 
updateDoubleLineClock()471     private void updateDoubleLineClock() {
472         mCanShowDoubleLineClock = mSecureSettings.getIntForUser(
473             Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1,
474                 UserHandle.USER_CURRENT) != 0;
475 
476         if (!mCanShowDoubleLineClock) {
477             mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
478         }
479     }
480 
setWeatherVisibility()481     private void setWeatherVisibility() {
482         if (mWeatherView != null) {
483             mUiExecutor.execute(
484                     () -> mWeatherView.setVisibility(
485                         mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE));
486         }
487     }
488 
489     /**
490      * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
491      * bounds during the unlock transition.
492      */
setClipChildrenForUnlock(boolean clip)493     private void setClipChildrenForUnlock(boolean clip) {
494         if (mStatusArea != null) {
495             mStatusArea.setClipChildren(clip);
496         }
497     }
498 
499     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)500     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
501         pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
502         pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
503         mView.dump(pw, args);
504         ClockController clock = getClock();
505         if (clock != null) {
506             clock.dump(pw);
507         }
508         final RegionSampler regionSampler = mClockEventController.getRegionSampler();
509         if (regionSampler != null) {
510             regionSampler.dump(pw);
511         }
512     }
513 
514     /** Gets the animations for the current clock. */
515     @Nullable
getClockAnimations()516     public ClockAnimations getClockAnimations() {
517         ClockController clock = getClock();
518         return clock == null ? null : clock.getAnimations();
519     }
520 }
521 
522