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