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 android.app.WallpaperManager; 23 import android.text.TextUtils; 24 import android.view.View; 25 import android.widget.FrameLayout; 26 import android.widget.RelativeLayout; 27 28 import com.android.internal.colorextraction.ColorExtractor; 29 import com.android.keyguard.clock.ClockManager; 30 import com.android.systemui.R; 31 import com.android.systemui.broadcast.BroadcastDispatcher; 32 import com.android.systemui.colorextraction.SysuiColorExtractor; 33 import com.android.systemui.keyguard.KeyguardUnlockAnimationController; 34 import com.android.systemui.plugins.ClockPlugin; 35 import com.android.systemui.plugins.statusbar.StatusBarStateController; 36 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController; 37 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; 38 import com.android.systemui.statusbar.notification.AnimatableProperty; 39 import com.android.systemui.statusbar.notification.PropertyAnimator; 40 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 41 import com.android.systemui.statusbar.phone.KeyguardBypassController; 42 import com.android.systemui.statusbar.phone.NotificationIconAreaController; 43 import com.android.systemui.statusbar.phone.NotificationIconContainer; 44 import com.android.systemui.statusbar.policy.BatteryController; 45 import com.android.systemui.util.ViewController; 46 47 import java.util.HashSet; 48 import java.util.Locale; 49 import java.util.Set; 50 import java.util.TimeZone; 51 52 import javax.inject.Inject; 53 54 /** 55 * Injectable controller for {@link KeyguardClockSwitch}. 56 */ 57 public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch> { 58 private static final boolean CUSTOM_CLOCKS_ENABLED = true; 59 60 private final StatusBarStateController mStatusBarStateController; 61 private final SysuiColorExtractor mColorExtractor; 62 private final ClockManager mClockManager; 63 private final KeyguardSliceViewController mKeyguardSliceViewController; 64 private final NotificationIconAreaController mNotificationIconAreaController; 65 private final BroadcastDispatcher mBroadcastDispatcher; 66 private final BatteryController mBatteryController; 67 private final LockscreenSmartspaceController mSmartspaceController; 68 69 /** 70 * Clock for both small and large sizes 71 */ 72 private AnimatableClockController mClockViewController; 73 private FrameLayout mClockFrame; 74 private AnimatableClockController mLargeClockViewController; 75 private FrameLayout mLargeClockFrame; 76 77 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 78 private final KeyguardBypassController mBypassController; 79 80 /** 81 * Listener for changes to the color palette. 82 * 83 * The color palette changes when the wallpaper is changed. 84 */ 85 private final ColorExtractor.OnColorsChangedListener mColorsListener = 86 (extractor, which) -> { 87 if ((which & WallpaperManager.FLAG_LOCK) != 0) { 88 mView.updateColors(getGradientColors()); 89 } 90 }; 91 92 private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; 93 94 // If set, will replace keyguard_status_area 95 private View mSmartspaceView; 96 97 private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; 98 private SmartspaceTransitionController mSmartspaceTransitionController; 99 100 private boolean mOnlyClock = false; 101 102 @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)103 public KeyguardClockSwitchController( 104 KeyguardClockSwitch keyguardClockSwitch, 105 StatusBarStateController statusBarStateController, 106 SysuiColorExtractor colorExtractor, 107 ClockManager clockManager, 108 KeyguardSliceViewController keyguardSliceViewController, 109 NotificationIconAreaController notificationIconAreaController, 110 BroadcastDispatcher broadcastDispatcher, 111 BatteryController batteryController, 112 KeyguardUpdateMonitor keyguardUpdateMonitor, 113 KeyguardBypassController bypassController, 114 LockscreenSmartspaceController smartspaceController, 115 KeyguardUnlockAnimationController keyguardUnlockAnimationController, 116 SmartspaceTransitionController smartspaceTransitionController) { 117 super(keyguardClockSwitch); 118 mStatusBarStateController = statusBarStateController; 119 mColorExtractor = colorExtractor; 120 mClockManager = clockManager; 121 mKeyguardSliceViewController = keyguardSliceViewController; 122 mNotificationIconAreaController = notificationIconAreaController; 123 mBroadcastDispatcher = broadcastDispatcher; 124 mBatteryController = batteryController; 125 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 126 mBypassController = bypassController; 127 mSmartspaceController = smartspaceController; 128 129 mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; 130 mSmartspaceTransitionController = smartspaceTransitionController; 131 } 132 133 /** 134 * Mostly used for alternate displays, limit the information shown 135 */ setOnlyClock(boolean onlyClock)136 public void setOnlyClock(boolean onlyClock) { 137 mOnlyClock = onlyClock; 138 } 139 140 /** 141 * Attach the controller to the view it relates to. 142 */ 143 @Override onInit()144 public void onInit() { 145 mKeyguardSliceViewController.init(); 146 147 mClockFrame = mView.findViewById(R.id.lockscreen_clock_view); 148 mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large); 149 150 mClockViewController = 151 new AnimatableClockController( 152 mView.findViewById(R.id.animatable_clock_view), 153 mStatusBarStateController, 154 mBroadcastDispatcher, 155 mBatteryController, 156 mKeyguardUpdateMonitor, 157 mBypassController); 158 mClockViewController.init(); 159 160 mLargeClockViewController = 161 new AnimatableClockController( 162 mView.findViewById(R.id.animatable_clock_view_large), 163 mStatusBarStateController, 164 mBroadcastDispatcher, 165 mBatteryController, 166 mKeyguardUpdateMonitor, 167 mBypassController); 168 mLargeClockViewController.init(); 169 } 170 171 @Override onViewAttached()172 protected void onViewAttached() { 173 if (CUSTOM_CLOCKS_ENABLED) { 174 mClockManager.addOnClockChangedListener(mClockChangedListener); 175 } 176 mColorExtractor.addOnColorsChangedListener(mColorsListener); 177 mView.updateColors(getGradientColors()); 178 179 if (mOnlyClock) { 180 View ksa = mView.findViewById(R.id.keyguard_status_area); 181 ksa.setVisibility(View.GONE); 182 183 View nic = mView.findViewById( 184 R.id.left_aligned_notification_icon_container); 185 nic.setVisibility(View.GONE); 186 return; 187 } 188 updateAodIcons(); 189 190 if (mSmartspaceController.isEnabled()) { 191 mSmartspaceView = mSmartspaceController.buildAndConnectView(mView); 192 193 View ksa = mView.findViewById(R.id.keyguard_status_area); 194 int ksaIndex = mView.indexOfChild(ksa); 195 ksa.setVisibility(View.GONE); 196 197 // Place smartspace view below normal clock... 198 RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( 199 MATCH_PARENT, WRAP_CONTENT); 200 lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view); 201 202 mView.addView(mSmartspaceView, ksaIndex, lp); 203 int startPadding = getContext().getResources() 204 .getDimensionPixelSize(R.dimen.below_clock_padding_start); 205 int endPadding = getContext().getResources() 206 .getDimensionPixelSize(R.dimen.below_clock_padding_end); 207 mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0); 208 209 updateClockLayout(); 210 211 View nic = mView.findViewById( 212 R.id.left_aligned_notification_icon_container); 213 lp = (RelativeLayout.LayoutParams) nic.getLayoutParams(); 214 lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId()); 215 nic.setLayoutParams(lp); 216 217 mView.setSmartspaceView(mSmartspaceView); 218 mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView); 219 } 220 } 221 getNotificationIconAreaHeight()222 int getNotificationIconAreaHeight() { 223 return mNotificationIconAreaController.getHeight(); 224 } 225 226 @Override onViewDetached()227 protected void onViewDetached() { 228 if (CUSTOM_CLOCKS_ENABLED) { 229 mClockManager.removeOnClockChangedListener(mClockChangedListener); 230 } 231 mColorExtractor.removeOnColorsChangedListener(mColorsListener); 232 mView.setClockPlugin(null, mStatusBarStateController.getState()); 233 234 mSmartspaceController.disconnect(); 235 236 // TODO: This is an unfortunate necessity since smartspace plugin retains a single instance 237 // of the smartspace view -- if we don't remove the view, it can't be reused by a later 238 // instance of this class. In order to fix this, we need to modify the plugin so that 239 // (a) we get a new view each time and (b) we can properly clean up an old view by making 240 // it unregister itself as a plugin listener. 241 if (mSmartspaceView != null) { 242 mView.removeView(mSmartspaceView); 243 mSmartspaceView = null; 244 } 245 } 246 247 /** 248 * Apply dp changes on font/scale change 249 */ onDensityOrFontScaleChanged()250 public void onDensityOrFontScaleChanged() { 251 mView.onDensityOrFontScaleChanged(); 252 253 updateClockLayout(); 254 } 255 updateClockLayout()256 private void updateClockLayout() { 257 if (mSmartspaceController.isEnabled()) { 258 RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, 259 MATCH_PARENT); 260 lp.topMargin = getContext().getResources().getDimensionPixelSize( 261 R.dimen.keyguard_large_clock_top_margin); 262 mLargeClockFrame.setLayoutParams(lp); 263 } 264 } 265 266 /** 267 * Set whether or not the lock screen is showing notifications. 268 */ setHasVisibleNotifications(boolean hasVisibleNotifications)269 public void setHasVisibleNotifications(boolean hasVisibleNotifications) { 270 if (mView.willSwitchToLargeClock(hasVisibleNotifications)) { 271 mLargeClockViewController.animateAppear(); 272 } 273 } 274 275 /** 276 * If we're presenting a custom clock of just the default one. 277 */ hasCustomClock()278 public boolean hasCustomClock() { 279 return mView.hasCustomClock(); 280 } 281 282 /** 283 * Get the clock text size. 284 */ getClockTextSize()285 public float getClockTextSize() { 286 return mView.getTextSize(); 287 } 288 289 /** 290 * Refresh clock. Called in response to TIME_TICK broadcasts. 291 */ refresh()292 void refresh() { 293 if (mClockViewController != null) { 294 mClockViewController.refreshTime(); 295 mLargeClockViewController.refreshTime(); 296 } 297 if (mSmartspaceController != null) { 298 mSmartspaceController.requestSmartspaceUpdate(); 299 } 300 301 mView.refresh(); 302 } 303 304 /** 305 * Update position of the view, with optional animation. Move the slice view and the clock 306 * slightly towards the center in order to prevent burn-in. Y positioning occurs at the 307 * view parent level. The large clock view will scale instead of using x position offsets, to 308 * keep the clock centered. 309 */ updatePosition(int x, float scale, AnimationProperties props, boolean animate)310 void updatePosition(int x, float scale, AnimationProperties props, boolean animate) { 311 x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x; 312 313 PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X, 314 x, props, animate); 315 PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X, 316 scale, props, animate); 317 PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y, 318 scale, props, animate); 319 320 if (mSmartspaceView != null) { 321 PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X, 322 x, props, animate); 323 324 // If we're unlocking with the SmartSpace shared element transition, let the controller 325 // know that it should re-position our SmartSpace. 326 if (mKeyguardUnlockAnimationController.isUnlockingWithSmartSpaceTransition()) { 327 mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition(); 328 } 329 } 330 331 mKeyguardSliceViewController.updatePosition(x, props, animate); 332 mNotificationIconAreaController.updatePosition(x, props, animate); 333 } 334 335 /** Sets an alpha value on every child view except for the smartspace. */ setChildrenAlphaExcludingSmartspace(float alpha)336 public void setChildrenAlphaExcludingSmartspace(float alpha) { 337 final Set<View> excludedViews = new HashSet<>(); 338 339 if (mSmartspaceView != null) { 340 excludedViews.add(mSmartspaceView); 341 } 342 343 setChildrenAlphaExcluding(alpha, excludedViews); 344 } 345 346 /** Sets an alpha value on every child view except for the views in the provided set. */ setChildrenAlphaExcluding(float alpha, Set<View> excludedViews)347 public void setChildrenAlphaExcluding(float alpha, Set<View> excludedViews) { 348 for (int i = 0; i < mView.getChildCount(); i++) { 349 final View child = mView.getChildAt(i); 350 351 if (!excludedViews.contains(child)) { 352 child.setAlpha(alpha); 353 } 354 } 355 } 356 updateTimeZone(TimeZone timeZone)357 void updateTimeZone(TimeZone timeZone) { 358 mView.onTimeZoneChanged(timeZone); 359 if (mClockViewController != null) { 360 mClockViewController.onTimeZoneChanged(timeZone); 361 mLargeClockViewController.onTimeZoneChanged(timeZone); 362 } 363 } 364 refreshFormat()365 void refreshFormat() { 366 if (mClockViewController != null) { 367 mClockViewController.refreshFormat(); 368 mLargeClockViewController.refreshFormat(); 369 } 370 } 371 updateAodIcons()372 private void updateAodIcons() { 373 NotificationIconContainer nic = (NotificationIconContainer) 374 mView.findViewById( 375 com.android.systemui.R.id.left_aligned_notification_icon_container); 376 mNotificationIconAreaController.setupAodIcons(nic); 377 } 378 setClockPlugin(ClockPlugin plugin)379 private void setClockPlugin(ClockPlugin plugin) { 380 mView.setClockPlugin(plugin, mStatusBarStateController.getState()); 381 } 382 getGradientColors()383 private ColorExtractor.GradientColors getGradientColors() { 384 return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK); 385 } 386 getCurrentLayoutDirection()387 private int getCurrentLayoutDirection() { 388 return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()); 389 } 390 } 391