diff options
9 files changed, 178 insertions, 68 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index 0e5f8c1c7a26..553453d7f79d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -18,13 +18,9 @@ package com.android.keyguard; import android.content.Context; import android.content.res.TypedArray; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; -import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -33,22 +29,10 @@ import androidx.annotation.Nullable; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; -import java.lang.ref.WeakReference; - /*** * Manages a number of views inside of the given layout. See below for a list of widgets. */ public abstract class KeyguardMessageArea extends TextView implements SecurityMessageDisplay { - /** Handler token posted with accessibility announcement runnables. */ - private static final Object ANNOUNCE_TOKEN = new Object(); - - /** - * Delay before speaking an accessibility announcement. Used to prevent - * lift-to-type from interrupting itself. - */ - private static final long ANNOUNCEMENT_DELAY = 250; - - private final Handler mHandler; private CharSequence mMessage; private boolean mIsVisible; @@ -65,7 +49,6 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe super(context, attrs); setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug - mHandler = new Handler(Looper.myLooper()); onThemeChanged(); } @@ -127,9 +110,6 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe private void securityMessageChanged(CharSequence message) { mMessage = message; update(); - mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN); - mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN, - (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY)); } private void clearMessage() { @@ -156,25 +136,4 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe /** Set the text color */ protected abstract void updateTextColor(); - - /** - * Runnable used to delay accessibility announcements. - */ - private static class AnnounceRunnable implements Runnable { - private final WeakReference<View> mHost; - private final CharSequence mTextToAnnounce; - - AnnounceRunnable(View host, CharSequence textToAnnounce) { - mHost = new WeakReference<View>(host); - mTextToAnnounce = textToAnnounce; - } - - @Override - public void run() { - final View host = mHost.get(); - if (host != null) { - host.announceForAccessibility(mTextToAnnounce); - } - } - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index 6a9216218d07..c1896fc641e0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -18,11 +18,17 @@ package com.android.keyguard; import android.content.res.ColorStateList; import android.content.res.Configuration; +import android.text.TextUtils; +import android.view.View; + +import androidx.annotation.VisibleForTesting; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.util.ViewController; +import java.lang.ref.WeakReference; + import javax.inject.Inject; /** @@ -31,8 +37,14 @@ import javax.inject.Inject; */ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> extends ViewController<T> { + /** + * Delay before speaking an accessibility announcement. Used to prevent + * lift-to-type from interrupting itself. + */ + private static final long ANNOUNCEMENT_DELAY = 250; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; + private final AnnounceRunnable mAnnounceRunnable; private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { public void onFinishedGoingToSleep(int why) { @@ -68,6 +80,7 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; + mAnnounceRunnable = new AnnounceRunnable(mView); } @Override @@ -100,6 +113,12 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> */ public void setMessage(CharSequence s, boolean animate) { mView.setMessage(s, animate); + CharSequence msg = mView.getText(); + if (!TextUtils.isEmpty(msg)) { + mView.removeCallbacks(mAnnounceRunnable); + mAnnounceRunnable.setTextToAnnounce(msg); + mView.postDelayed(mAnnounceRunnable, ANNOUNCEMENT_DELAY); + } } public void setMessage(int resId) { @@ -134,4 +153,30 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> view, mKeyguardUpdateMonitor, mConfigurationController); } } + + /** + * Runnable used to delay accessibility announcements. + */ + @VisibleForTesting + public static class AnnounceRunnable implements Runnable { + private final WeakReference<View> mHost; + private CharSequence mTextToAnnounce; + + AnnounceRunnable(View host) { + mHost = new WeakReference<>(host); + } + + /** Sets the text to announce. */ + public void setTextToAnnounce(CharSequence textToAnnounce) { + mTextToAnnounce = textToAnnounce; + } + + @Override + public void run() { + final View host = mHost.get(); + if (host != null) { + host.announceForAccessibility(mTextToAnnounce); + } + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 2c2caea60f5a..c098d4ca286d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -1065,23 +1065,28 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } private void reloadColors() { - reinflateViewFlipper(); - mView.reloadColors(); + reinflateViewFlipper(() -> mView.reloadColors()); } /** Handles density or font scale changes. */ private void onDensityOrFontScaleChanged() { - reinflateViewFlipper(); - mView.onDensityOrFontScaleChanged(); + reinflateViewFlipper(() -> mView.onDensityOrFontScaleChanged()); } /** * Reinflate the view flipper child view. */ - public void reinflateViewFlipper() { + public void reinflateViewFlipper( + KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener) { mSecurityViewFlipperController.clearViews(); - mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode, - mKeyguardSecurityCallback); + if (mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)) { + mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode, + mKeyguardSecurityCallback, onViewInflatedListener); + } else { + mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode, + mKeyguardSecurityCallback); + onViewInflatedListener.onViewInflated(); + } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index 39b567fd21b9..68e1dd7d8eab 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -19,11 +19,16 @@ package com.android.keyguard; import android.util.Log; import android.view.LayoutInflater; +import androidx.annotation.Nullable; +import androidx.asynclayoutinflater.view.AsyncLayoutInflater; + import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardInputViewController.Factory; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.dagger.KeyguardBouncerScope; import com.android.systemui.R; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.util.ViewController; import java.util.ArrayList; @@ -44,18 +49,24 @@ public class KeyguardSecurityViewFlipperController private final List<KeyguardInputViewController<KeyguardInputView>> mChildren = new ArrayList<>(); private final LayoutInflater mLayoutInflater; + private final AsyncLayoutInflater mAsyncLayoutInflater; private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final Factory mKeyguardSecurityViewControllerFactory; + private final FeatureFlags mFeatureFlags; @Inject protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view, LayoutInflater layoutInflater, + AsyncLayoutInflater asyncLayoutInflater, KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory, - EmergencyButtonController.Factory emergencyButtonControllerFactory) { + EmergencyButtonController.Factory emergencyButtonControllerFactory, + FeatureFlags featureFlags) { super(view); mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory; mLayoutInflater = layoutInflater; mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; + mAsyncLayoutInflater = asyncLayoutInflater; + mFeatureFlags = featureFlags; } @Override @@ -92,13 +103,12 @@ public class KeyguardSecurityViewFlipperController } } - if (childController == null + if (!mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER) && childController == null && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) { - int layoutId = getLayoutIdFor(securityMode); KeyguardInputView view = null; if (layoutId != 0) { - if (DEBUG) Log.v(TAG, "inflating id = " + layoutId); + if (DEBUG) Log.v(TAG, "inflating on main thread id = " + layoutId); view = (KeyguardInputView) mLayoutInflater.inflate( layoutId, mView, false); mView.addView(view); @@ -119,6 +129,36 @@ public class KeyguardSecurityViewFlipperController return childController; } + /** + * Asynchronously inflate view and then add it to view flipper on the main thread when complete. + * + * OnInflateFinishedListener will be called on the main thread. + * + * @param securityMode + * @param keyguardSecurityCallback + */ + public void asynchronouslyInflateView(SecurityMode securityMode, + KeyguardSecurityCallback keyguardSecurityCallback, + @Nullable OnViewInflatedListener onViewInflatedListener) { + int layoutId = getLayoutIdFor(securityMode); + if (layoutId != 0) { + if (DEBUG) Log.v(TAG, "inflating on bg thread id = " + layoutId); + mAsyncLayoutInflater.inflate(layoutId, mView, + (view, resId, parent) -> { + mView.addView(view); + KeyguardInputViewController<KeyguardInputView> childController = + mKeyguardSecurityViewControllerFactory.create( + (KeyguardInputView) view, securityMode, + keyguardSecurityCallback); + childController.init(); + mChildren.add(childController); + if (onViewInflatedListener != null) { + onViewInflatedListener.onViewInflated(); + } + }); + } + } + private int getLayoutIdFor(SecurityMode securityMode) { switch (securityMode) { case Pattern: return R.layout.keyguard_pattern_view; @@ -162,4 +202,10 @@ public class KeyguardSecurityViewFlipperController return 0; } } + + /** Listener to when view has finished inflation. */ + public interface OnViewInflatedListener { + /** Notifies that view has been inflated */ + void onViewInflated(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index d1c34a8d0821..4fa40757612b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -97,6 +97,7 @@ import android.view.accessibility.CaptioningManager; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassificationManager; +import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import androidx.core.app.NotificationManagerCompat; import com.android.internal.app.IBatteryStats; @@ -388,6 +389,13 @@ public class FrameworkServicesModule { return LayoutInflater.from(context); } + /** */ + @Provides + @Singleton + public AsyncLayoutInflater provideAsyncLayoutInflater(Context context) { + return new AsyncLayoutInflater(context); + } + @Provides static MediaProjectionManager provideMediaProjectionManager(Context context) { return context.getSystemService(MediaProjectionManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index d7167845419b..5fcf1052d949 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -34,7 +34,6 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter import kotlinx.coroutines.awaitCancellation -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @@ -112,16 +111,18 @@ object KeyguardBouncerViewBinder { launch { viewModel.show.collect { // Reset Security Container entirely. - view.visibility = View.VISIBLE - securityContainerController.onBouncerVisibilityChanged( - /* isVisible= */ true - ) - securityContainerController.reinflateViewFlipper() - securityContainerController.showPrimarySecurityScreen( - /* turningOff= */ false - ) - securityContainerController.appear() - securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON) + securityContainerController.reinflateViewFlipper { + // Reset Security Container entirely. + view.visibility = View.VISIBLE + securityContainerController.onBouncerVisibilityChanged( + /* isVisible= */ true + ) + securityContainerController.showPrimarySecurityScreen( + /* turningOff= */ false + ) + securityContainerController.appear() + securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON) + } } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index 0e837d2976ba..a35e5b59f765 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -18,12 +18,15 @@ package com.android.keyguard; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -37,6 +40,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest +@TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner.class) public class KeyguardMessageAreaControllerTest extends SysuiTestCase { @Mock @@ -45,14 +49,14 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private KeyguardMessageArea mKeyguardMessageArea; - private KeyguardMessageAreaController mMessageAreaController; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mMessageAreaController = new KeyguardMessageAreaController.Factory( - mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea); + mKeyguardUpdateMonitor, mConfigurationController).create( + mKeyguardMessageArea); } @Test @@ -89,6 +93,19 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { } @Test + public void testSetMessage_AnnounceForAccessibility() { + ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class); + when(mKeyguardMessageArea.getText()).thenReturn("abc"); + mMessageAreaController.setMessage("abc"); + + verify(mKeyguardMessageArea).setMessage("abc", /* animate= */ true); + verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class)); + verify(mKeyguardMessageArea).postDelayed(argumentCaptor.capture(), anyLong()); + argumentCaptor.getValue().run(); + verify(mKeyguardMessageArea).announceForAccessibility("abc"); + } + + @Test public void testSetBouncerVisible() { mMessageAreaController.setIsVisible(true); verify(mKeyguardMessageArea).setIsVisible(true); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 4dc4c2cbf93c..26d20c24cb84 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -66,6 +66,7 @@ import com.android.systemui.biometrics.SideFpsUiRequestSource; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -617,13 +618,26 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void testReinflateViewFlipper() { - mKeyguardSecurityContainerController.reinflateViewFlipper(); + mKeyguardSecurityContainerController.reinflateViewFlipper(() -> {}); verify(mKeyguardSecurityViewFlipperController).clearViews(); verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), any(KeyguardSecurityCallback.class)); } @Test + public void testReinflateViewFlipper_asyncBouncerFlagOn() { + when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true); + KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener = + () -> { + }; + mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedListener); + verify(mKeyguardSecurityViewFlipperController).clearViews(); + verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( + any(SecurityMode.class), + any(KeyguardSecurityCallback.class), eq(onViewInflatedListener)); + } + + @Test public void testSideFpsControllerShow() { mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true); verify(mSideFpsController).show( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index 1614b577a6cc..afb54d2df49f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -31,10 +31,12 @@ import android.view.LayoutInflater; import android.view.ViewGroup; import android.view.WindowInsetsController; +import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; import org.junit.Before; import org.junit.Rule; @@ -57,6 +59,8 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { @Mock private LayoutInflater mLayoutInflater; @Mock + private AsyncLayoutInflater mAsyncLayoutInflater; + @Mock private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory; @Mock private EmergencyButtonController.Factory mEmergencyButtonControllerFactory; @@ -70,6 +74,8 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { private WindowInsetsController mWindowInsetsController; @Mock private KeyguardSecurityCallback mKeyguardSecurityCallback; + @Mock + private FeatureFlags mFeatureFlags; private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; @@ -82,10 +88,11 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController); when(mEmergencyButtonControllerFactory.create(any(EmergencyButton.class))) .thenReturn(mEmergencyButtonController); + when(mView.getContext()).thenReturn(getContext()); mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView, - mLayoutInflater, mKeyguardSecurityViewControllerFactory, - mEmergencyButtonControllerFactory); + mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory, + mEmergencyButtonControllerFactory, mFeatureFlags); } @Test @@ -108,6 +115,14 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { } @Test + public void asynchronouslyInflateView() { + mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN, + mKeyguardSecurityCallback, null); + verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), any( + AsyncLayoutInflater.OnInflateFinishedListener.class)); + } + + @Test public void onDensityOrFontScaleChanged() { mKeyguardSecurityViewFlipperController.clearViews(); verify(mView).removeAllViews(); |