Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3d244cce authored by Hawkwood Glazier's avatar Hawkwood Glazier
Browse files

Remove AnimatableClockController in favor of ClockEventController

Bug: 229771520
Test: Automated
Change-Id: I10a18f0e753962064f9ac12f3d15f955ce419cbf
parent 5e56c83b
Loading
Loading
Loading
Loading
+0 −302
Original line number Diff line number Diff line
/*
 * 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.icu.text.NumberFormat;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;

import java.io.PrintWriter;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.Executor;

import javax.inject.Inject;

/**
 * Controller for an AnimatableClockView on the keyguard. Instantiated by
 * {@link KeyguardClockSwitchController}.
 */
public class AnimatableClockController extends ViewController<AnimatableClockView> {
    private static final String TAG = "AnimatableClockCtrl";
    private static final int FORMAT_NUMBER = 1234567890;

    private final StatusBarStateController mStatusBarStateController;
    private final BroadcastDispatcher mBroadcastDispatcher;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    private final BatteryController mBatteryController;
    private final int mDozingColor = Color.WHITE;
    private Optional<RegionSamplingHelper> mRegionSamplingHelper = Optional.empty();
    private Rect mSamplingBounds = new Rect();
    private int mLockScreenColor;
    private final boolean mRegionSamplingEnabled;

    private boolean mIsDozing;
    private boolean mIsCharging;
    private float mDozeAmount;
    boolean mKeyguardShowing;
    private Locale mLocale;

    private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
    private final String mBurmeseNumerals;
    private final float mBurmeseLineSpacing;
    private final float mDefaultLineSpacing;

    @Inject
    public AnimatableClockController(
            AnimatableClockView view,
            StatusBarStateController statusBarStateController,
            BroadcastDispatcher broadcastDispatcher,
            BatteryController batteryController,
            KeyguardUpdateMonitor keyguardUpdateMonitor,
            @Main Resources resources,
            @Main Executor mainExecutor,
            @Background Executor bgExecutor,
            FeatureFlags featureFlags
    ) {
        super(view);
        mStatusBarStateController = statusBarStateController;
        mBroadcastDispatcher = broadcastDispatcher;
        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
        mBatteryController = batteryController;

        mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
        mBurmeseLineSpacing = resources.getFloat(
                R.dimen.keyguard_clock_line_spacing_scale_burmese);
        mDefaultLineSpacing = resources.getFloat(
                R.dimen.keyguard_clock_line_spacing_scale);

        mRegionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING);
        if (!mRegionSamplingEnabled) {
            return;
        }

        mRegionSamplingHelper = Optional.of(new RegionSamplingHelper(mView,
                new RegionSamplingHelper.SamplingCallback() {
                    @Override
                    public void onRegionDarknessChanged(boolean isRegionDark) {
                        if (isRegionDark) {
                            mLockScreenColor = Color.WHITE;
                        } else {
                            mLockScreenColor = Color.BLACK;
                        }
                        initColors();
                    }

                    @Override
                    public Rect getSampledRegion(View sampledView) {
                        mSamplingBounds = new Rect(sampledView.getLeft(), sampledView.getTop(),
                                sampledView.getRight(), sampledView.getBottom());
                        return mSamplingBounds;
                    }

                    @Override
                    public boolean isSamplingEnabled() {
                        return mRegionSamplingEnabled;
                    }
                }, mainExecutor, bgExecutor)
        );
        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
            regionSamplingHelper.setWindowVisible(true);
        });
    }

    private void reset() {
        mView.animateDoze(mIsDozing, false);
    }

    private final BatteryController.BatteryStateChangeCallback mBatteryCallback =
            new BatteryController.BatteryStateChangeCallback() {
        @Override
        public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
            if (mKeyguardShowing && !mIsCharging && charging) {
                mView.animateCharge(mStatusBarStateController::isDozing);
            }
            mIsCharging = charging;
        }
    };

    private final BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            updateLocale();
        }
    };

    private final StatusBarStateController.StateListener mStatusBarStateListener =
            new StatusBarStateController.StateListener() {
                @Override
                public void onDozeAmountChanged(float linear, float eased) {
                    boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
                            || (mDozeAmount == 1f && linear == 0f);
                    boolean isDozing = linear > mDozeAmount;
                    mDozeAmount = linear;
                    if (mIsDozing != isDozing) {
                        mIsDozing = isDozing;
                        mView.animateDoze(mIsDozing, !noAnimation);
                    }
                }
            };

    private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
            new KeyguardUpdateMonitorCallback() {
        @Override
        public void onKeyguardVisibilityChanged(boolean showing) {
            mKeyguardShowing = showing;
            if (!mKeyguardShowing) {
                // reset state (ie: after weight animations)
                reset();
            }
        }

        @Override
        public void onTimeFormatChanged(String timeFormat) {
            mView.refreshFormat();
        }

        @Override
        public void onTimeZoneChanged(TimeZone timeZone) {
            mView.onTimeZoneChanged(timeZone);
        }

        @Override
        public void onUserSwitchComplete(int userId) {
            mView.refreshFormat();
        }
    };

    @Override
    protected void onInit() {
        mIsDozing = mStatusBarStateController.isDozing();
    }

    @Override
    protected void onViewAttached() {
        updateLocale();
        mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
        mDozeAmount = mStatusBarStateController.getDozeAmount();
        mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
        mBatteryController.addCallback(mBatteryCallback);
        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);

        mStatusBarStateController.addCallback(mStatusBarStateListener);

        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
            regionSamplingHelper.start(mSamplingBounds);
        });

        refreshTime();
        initColors();
        mView.animateDoze(mIsDozing, false);
    }

    @Override
    protected void onViewDetached() {
        mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
        mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
        mBatteryController.removeCallback(mBatteryCallback);
        mStatusBarStateController.removeCallback(mStatusBarStateListener);
        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
            regionSamplingHelper.stop();
        });
    }

    /** Animate the clock appearance */
    public void animateAppear() {
        if (!mIsDozing) mView.animateAppearOnLockscreen();
    }

    /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
     * fully folded state and it goes to sleep (always on display screen) */
    public void animateFoldAppear() {
        mView.animateFoldAppear(true);
    }

    /**
     * Updates the time for the view.
     */
    public void refreshTime() {
        mView.refreshTime();
    }

    /**
     * Return locallly stored dozing state.
     */
    @VisibleForTesting
    public boolean isDozing() {
        return mIsDozing;
    }

    private void updateLocale() {
        Locale currLocale = Locale.getDefault();
        if (!Objects.equals(currLocale, mLocale)) {
            mLocale = currLocale;
            NumberFormat nf = NumberFormat.getInstance(mLocale);
            if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
                mView.setLineSpacingScale(mBurmeseLineSpacing);
            } else {
                mView.setLineSpacingScale(mDefaultLineSpacing);
            }
            mView.refreshFormat();
        }
    }

    private void initColors() {
        if (!mRegionSamplingEnabled) {
            mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
                    com.android.systemui.R.attr.wallpaperTextColorAccent);
        }
        mView.setColors(mDozingColor, mLockScreenColor);
        mView.animateDoze(mIsDozing, false);
    }

    /**
     * Dump information for debugging
     */
    public void dump(@NonNull PrintWriter pw) {
        pw.println(this);
        mView.dump(pw);
        mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
            regionSamplingHelper.dump(pw);
        });
    }
}
+0 −152
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.systemui.keyguard;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;

import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;

import androidx.test.filters.SmallTest;

import com.android.keyguard.AnimatableClockController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.policy.BatteryController;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;

import java.util.concurrent.Executor;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class AnimatableClockControllerTest extends SysuiTestCase {
    @Mock
    private AnimatableClockView mClockView;
    @Mock
    private StatusBarStateController mStatusBarStateController;
    @Mock
    private BroadcastDispatcher mBroadcastDispatcher;
    @Mock
    private BatteryController mBatteryController;
    @Mock
    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    @Mock
    private Resources mResources;
    @Mock
    private Executor mMainExecutor;
    @Mock
    private Executor mBgExecutor;
    @Mock
    private FeatureFlags mFeatureFlags;

    private MockitoSession mStaticMockSession;
    private AnimatableClockController mAnimatableClockController;

    // Capture listeners so that they can be used to send events
    @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
    private View.OnAttachStateChangeListener mAttachListener;

    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
    private StatusBarStateController.StateListener mStatusBarStateCallback;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mStaticMockSession = mockitoSession()
                .mockStatic(Utils.class)
                .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
                .startMocking();
        when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);

        mAnimatableClockController = new AnimatableClockController(
                mClockView,
                mStatusBarStateController,
                mBroadcastDispatcher,
                mBatteryController,
                mKeyguardUpdateMonitor,
                mResources,
                mMainExecutor,
                mBgExecutor,
                mFeatureFlags
        );
        mAnimatableClockController.init();
        captureAttachListener();
    }

    @After
    public void tearDown() {
        mStaticMockSession.finishMocking();
    }

    @Test
    public void testOnAttachedUpdatesDozeStateToTrue() {
        // GIVEN dozing
        when(mStatusBarStateController.isDozing()).thenReturn(true);
        when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);

        // WHEN the clock view gets attached
        mAttachListener.onViewAttachedToWindow(mClockView);

        // THEN the clock controller updated its dozing state to true
        assertTrue(mAnimatableClockController.isDozing());
    }

    @Test
    public void testOnAttachedUpdatesDozeStateToFalse() {
        // GIVEN not dozing
        when(mStatusBarStateController.isDozing()).thenReturn(false);
        when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);

        // WHEN the clock view gets attached
        mAttachListener.onViewAttachedToWindow(mClockView);

        // THEN the clock controller updated its dozing state to false
        assertFalse(mAnimatableClockController.isDozing());
    }

    private void captureAttachListener() {
        verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
        mAttachListener = mAttachCaptor.getValue();
    }
}