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

Commit 25c0723d authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Remove AnimatableClockController in favor of ClockEventController" into...

Merge "Remove AnimatableClockController in favor of ClockEventController" into tm-qpr-dev am: a1cd1e09

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19833094



Change-Id: Ie22f5825413b06bd1c2145dd9ef1aeca310881b6
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 4ebdb634 a1cd1e09
Loading
Loading
Loading
Loading
+0 −302
Original line number Original line 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);
        });

        mView.onTimeZoneChanged(TimeZone.getDefault());
        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 Original line 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();
    }
}