blob: 6c32a4910c563e02dedab6c75fb8547ef86b47ae [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.keyguard;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import android.app.WallpaperManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* Injectable controller for {@link KeyguardClockSwitch}.
*/
public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
implements Dumpable {
private static final boolean CUSTOM_CLOCKS_ENABLED = true;
private final StatusBarStateController mStatusBarStateController;
private final SysuiColorExtractor mColorExtractor;
private final ClockManager mClockManager;
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
private final Resources mResources;
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
/**
* Clock for both small and large sizes
*/
private AnimatableClockController mClockViewController;
private FrameLayout mClockFrame; // top aligned clock
private AnimatableClockController mLargeClockViewController;
private FrameLayout mLargeClockFrame; // centered clock
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private int mKeyguardClockTopMargin = 0;
/**
* Listener for changes to the color palette.
*
* The color palette changes when the wallpaper is changed.
*/
private final ColorExtractor.OnColorsChangedListener mColorsListener =
(extractor, which) -> {
if ((which & WallpaperManager.FLAG_LOCK) != 0) {
mView.updateColors(getGradientColors());
}
};
private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
private ViewGroup mStatusArea;
// If set will replace keyguard_slice_view
private View mSmartspaceView;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private boolean mOnlyClock = false;
private Executor mUiExecutor;
private boolean mCanShowDoubleLineClock = true;
private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
updateDoubleLineClock();
}
};
private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
mKeyguardUnlockAnimationListener =
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@Override
public void onUnlockAnimationFinished() {
// For performance reasons, reset this once the unlock animation ends.
setClipChildrenForUnlock(true);
}
};
@Inject
public KeyguardClockSwitchController(
KeyguardClockSwitch keyguardClockSwitch,
StatusBarStateController statusBarStateController,
SysuiColorExtractor colorExtractor,
ClockManager clockManager,
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
BroadcastDispatcher broadcastDispatcher,
BatteryController batteryController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main Executor uiExecutor,
@Main Resources resources,
DumpManager dumpManager) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mColorExtractor = colorExtractor;
mClockManager = clockManager;
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
mBroadcastDispatcher = broadcastDispatcher;
mBatteryController = batteryController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mSmartspaceController = smartspaceController;
mResources = resources;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
}
/**
* Mostly used for alternate displays, limit the information shown
*/
public void setOnlyClock(boolean onlyClock) {
mOnlyClock = onlyClock;
}
/**
* Attach the controller to the view it relates to.
*/
@Override
public void onInit() {
mKeyguardSliceViewController.init();
mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
mClockViewController =
new AnimatableClockController(
mView.findViewById(R.id.animatable_clock_view),
mStatusBarStateController,
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
mResources);
mClockViewController.init();
mLargeClockViewController =
new AnimatableClockController(
mView.findViewById(R.id.animatable_clock_view_large),
mStatusBarStateController,
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
mResources);
mLargeClockViewController.init();
mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
mDumpManager.registerDumpable(getClass().toString(), this);
}
@Override
protected void onViewAttached() {
if (CUSTOM_CLOCKS_ENABLED) {
mClockManager.addOnClockChangedListener(mClockChangedListener);
}
mColorExtractor.addOnColorsChangedListener(mColorsListener);
mView.updateColors(getGradientColors());
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
View ksv = mView.findViewById(R.id.keyguard_slice_view);
ksv.setVisibility(View.GONE);
View nic = mView.findViewById(
R.id.left_aligned_notification_icon_container);
nic.setVisibility(View.GONE);
return;
}
updateAodIcons();
mStatusArea = mView.findViewById(R.id.keyguard_status_area);
if (mSmartspaceController.isEnabled()) {
View ksv = mView.findViewById(R.id.keyguard_slice_view);
int ksvIndex = mStatusArea.indexOfChild(ksv);
ksv.setVisibility(View.GONE);
addSmartspaceView(ksvIndex);
updateClockLayout();
}
mSecureSettings.registerContentObserverForUser(
Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
false, /* notifyForDescendants */
mDoubleLineClockObserver,
UserHandle.USER_ALL
);
updateDoubleLineClock();
mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
}
int getNotificationIconAreaHeight() {
return mNotificationIconAreaController.getHeight();
}
@Override
protected void onViewDetached() {
if (CUSTOM_CLOCKS_ENABLED) {
mClockManager.removeOnClockChangedListener(mClockChangedListener);
}
mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState());
mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
}
void onLocaleListChanged() {
if (mSmartspaceController.isEnabled()) {
int index = mStatusArea.indexOfChild(mSmartspaceView);
if (index >= 0) {
mStatusArea.removeView(mSmartspaceView);
addSmartspaceView(index);
}
}
}
private void addSmartspaceView(int index) {
mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
mStatusArea.addView(mSmartspaceView, index, lp);
int startPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_end);
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
}
/**
* Apply dp changes on font/scale change
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
updateClockLayout();
}
private void updateClockLayout() {
int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
R.dimen.keyguard_large_clock_top_margin)
- (int) mLargeClockViewController.getBottom();
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
lp.topMargin = largeClockTopMargin;
mLargeClockFrame.setLayoutParams(lp);
}
/**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize, boolean animate) {
if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
return;
}
mCurrentClockSize = clockSize;
boolean appeared = mView.switchToClock(clockSize, animate);
if (animate && appeared && clockSize == LARGE) {
mLargeClockViewController.animateAppear();
}
}
public void animateFoldToAod() {
if (mClockViewController != null) {
mClockViewController.animateFoldAppear();
mLargeClockViewController.animateFoldAppear();
}
}
/**
* If we're presenting a custom clock of just the default one.
*/
public boolean hasCustomClock() {
return mView.hasCustomClock();
}
/**
* Get the clock text size.
*/
public float getClockTextSize() {
return mView.getTextSize();
}
/**
* Refresh clock. Called in response to TIME_TICK broadcasts.
*/
void refresh() {
if (mClockViewController != null) {
mClockViewController.refreshTime();
mLargeClockViewController.refreshTime();
}
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
mView.refresh();
}
/**
* Update position of the view, with optional animation. Move the slice view and the clock
* slightly towards the center in order to prevent burn-in. Y positioning occurs at the
* view parent level. The large clock view will scale instead of using x position offsets, to
* keep the clock centered.
*/
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
x, props, animate);
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
scale, props, animate);
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
scale, props, animate);
if (mStatusArea != null) {
PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
}
}
void updateTimeZone(TimeZone timeZone) {
mView.onTimeZoneChanged(timeZone);
}
/**
* Get y-bottom position of the currently visible clock on the keyguard.
* We can't directly getBottom() because clock changes positions in AOD for burn-in
*/
int getClockBottom(int statusBarHeaderHeight) {
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
View clock = mLargeClockFrame.findViewById(
com.android.systemui.R.id.animatable_clock_view_large);
int frameHeight = mLargeClockFrame.getHeight();
int clockHeight = clock.getHeight();
return frameHeight / 2 + clockHeight / 2;
} else {
return mClockFrame.findViewById(
com.android.systemui.R.id.animatable_clock_view).getHeight()
+ statusBarHeaderHeight + mKeyguardClockTopMargin;
}
}
boolean isClockTopAligned() {
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
private void updateAodIcons() {
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
com.android.systemui.R.id.left_aligned_notification_icon_container);
mNotificationIconAreaController.setupAodIcons(nic);
}
private void setClockPlugin(ClockPlugin plugin) {
mView.setClockPlugin(plugin, mStatusBarStateController.getState());
}
private ColorExtractor.GradientColors getGradientColors() {
return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
}
private int getCurrentLayoutDirection() {
return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
}
private void updateDoubleLineClock() {
mCanShowDoubleLineClock = mSecureSettings.getIntForUser(
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1,
UserHandle.USER_CURRENT) != 0;
if (!mCanShowDoubleLineClock) {
mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
}
}
/**
* Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
* bounds during the unlock transition.
*/
private void setClipChildrenForUnlock(boolean clip) {
mView.setClipChildren(clip);
if (mStatusArea != null) {
mStatusArea.setClipChildren(clip);
}
}
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
mClockViewController.dump(pw);
mLargeClockViewController.dump(pw);
}
}