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