diff options
13 files changed, 508 insertions, 10 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 83e6a54e272b..a0a876825a6f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -701,4 +701,10 @@ <!-- How long in milliseconds before full burn-in protection is achieved. --> <integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer> + + <integer name="complicationFadeOutMs">500</integer> + + <integer name="complicationFadeInMs">500</integer> + + <integer name="complicationRestoreMs">1000</integer> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java index fe458f4c5318..51bd31100ad0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java @@ -186,6 +186,19 @@ public interface Complication { } /** + * The implementation of this interface is in charge of managing the visible state of + * the shown complication. + */ + interface VisibilityController { + /** + * Called to set the visibility of all shown and future complications. + * @param visibility The desired future visibility. + * @param animate whether the change should be animated. + */ + void setVisibility(@View.Visibility int visibility, boolean animate); + } + + /** * Returned through {@link Complication#createView(ComplicationViewModel)}, {@link ViewHolder} * is a container for a single {@link Complication} instance. The {@link Host} guarantees that * the {@link ViewHolder} will be retained for the lifetime of the {@link Complication} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java index 2999c723f634..aa433832d462 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java @@ -16,17 +16,23 @@ package com.android.systemui.dreams.complication; +import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_IN_DURATION; +import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DURATION; import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN; import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Constraints; import com.android.systemui.R; +import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.touch.TouchInsetManager; import java.util.ArrayList; @@ -43,7 +49,8 @@ import javax.inject.Named; * their layout parameters and attributes. The management of this set is done by * {@link ComplicationHostViewController}. */ -public class ComplicationLayoutEngine { +@DreamOverlayComponent.DreamOverlayScope +public class ComplicationLayoutEngine implements Complication.VisibilityController { public static final String TAG = "ComplicationLayoutEngine"; /** @@ -454,15 +461,45 @@ public class ComplicationLayoutEngine { private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>(); private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>(); private final TouchInsetManager.TouchInsetSession mSession; + private final int mFadeInDuration; + private final int mFadeOutDuration; + private ViewPropertyAnimator mViewPropertyAnimator; /** */ @Inject public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout, @Named(COMPLICATION_MARGIN) int margin, - TouchInsetManager.TouchInsetSession session) { + TouchInsetManager.TouchInsetSession session, + @Named(COMPLICATIONS_FADE_IN_DURATION) int fadeInDuration, + @Named(COMPLICATIONS_FADE_OUT_DURATION) int fadeOutDuration) { mLayout = layout; mMargin = margin; mSession = session; + mFadeInDuration = fadeInDuration; + mFadeOutDuration = fadeOutDuration; + } + + @Override + public void setVisibility(int visibility, boolean animate) { + final boolean appearing = visibility == View.VISIBLE; + + if (mViewPropertyAnimator != null) { + mViewPropertyAnimator.cancel(); + } + + if (appearing) { + mLayout.setVisibility(View.VISIBLE); + } + + mViewPropertyAnimator = mLayout.animate() + .alpha(appearing ? 1f : 0f) + .setDuration(appearing ? mFadeInDuration : mFadeOutDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mLayout.setVisibility(visibility); + } + }); } /** @@ -477,6 +514,8 @@ public class ComplicationLayoutEngine { */ public void addComplication(ComplicationId id, View view, ComplicationLayoutParams lp, @Complication.Category int category) { + Log.d(TAG, "engine: " + this + " addComplication"); + // If the complication is present, remove. if (mEntries.containsKey(id)) { removeComplication(id); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java index f701173c26f6..11d89d2dc816 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java @@ -38,6 +38,9 @@ import dagger.Provides; public abstract class ComplicationHostViewModule { public static final String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout"; public static final String COMPLICATION_MARGIN = "complication_margin"; + public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration"; + public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration"; + public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"; /** * Generates a {@link ConstraintLayout}, which can host @@ -60,4 +63,34 @@ public abstract class ComplicationHostViewModule { static int providesComplicationPadding(@Main Resources resources) { return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin); } + + /** + * Provides the fade out duration for complications. + */ + @Provides + @Named(COMPLICATIONS_FADE_OUT_DURATION) + @DreamOverlayComponent.DreamOverlayScope + static int providesComplicationsFadeOutDuration(@Main Resources resources) { + return resources.getInteger(R.integer.complicationFadeOutMs); + } + + /** + * Provides the fade in duration for complications. + */ + @Provides + @Named(COMPLICATIONS_FADE_IN_DURATION) + @DreamOverlayComponent.DreamOverlayScope + static int providesComplicationsFadeInDuration(@Main Resources resources) { + return resources.getInteger(R.integer.complicationFadeInMs); + } + + /** + * Provides the timeout for restoring complication visibility. + */ + @Provides + @Named(COMPLICATIONS_RESTORE_TIMEOUT) + @DreamOverlayComponent.DreamOverlayScope + static int providesComplicationsRestoreTimeout(@Main Resources resources) { + return resources.getInteger(R.integer.complicationRestoreMs); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java index 785a95df930d..5c2fdf5c9af4 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java @@ -21,7 +21,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStore; +import com.android.systemui.dreams.complication.Complication; import com.android.systemui.dreams.complication.ComplicationCollectionViewModel; +import com.android.systemui.dreams.complication.ComplicationLayoutEngine; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -62,4 +64,13 @@ public interface ComplicationModule { return provider.get(ComplicationCollectionViewModel.class); } + + /** + * Provides the visibility controller for display complications. + */ + @Provides + static Complication.VisibilityController providesVisibilityController( + ComplicationLayoutEngine engine) { + return engine; + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 65f060b43c08..de5b4bb9e520 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -21,7 +21,6 @@ import android.content.Context; import com.android.settingslib.dream.DreamBackend; import com.android.systemui.dreams.complication.dagger.DreamPreviewComplicationComponent; import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule; -import com.android.systemui.dreams.touch.dagger.DreamTouchModule; import dagger.Module; import dagger.Provides; @@ -30,7 +29,6 @@ import dagger.Provides; * Dagger Module providing Communal-related functionality. */ @Module(includes = { - DreamTouchModule.class, RegisteredComplicationsModule.class, }, subcomponents = { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java index 05ab9015fecc..f927ba64a92b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java @@ -26,6 +26,7 @@ import com.android.systemui.dreams.DreamOverlayContainerViewController; import com.android.systemui.dreams.complication.Complication; import com.android.systemui.dreams.complication.dagger.ComplicationModule; import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor; +import com.android.systemui.dreams.touch.dagger.DreamTouchModule; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -39,6 +40,7 @@ import dagger.Subcomponent; * Dagger subcomponent for {@link DreamOverlayModule}. */ @Subcomponent(modules = { + DreamTouchModule.class, DreamOverlayModule.class, ComplicationModule.class, }) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java new file mode 100644 index 000000000000..d4ba2d82f38a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 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.dreams.touch; + +import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT; + +import android.os.Handler; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.complication.Complication; +import com.android.systemui.touch.TouchInsetManager; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * {@link HideComplicationTouchHandler} is responsible for hiding the overlay complications from + * visibility whenever there is touch interactions outside the overlay. The overlay interaction + * scope includes touches to the complication plus any touch entry region for gestures as specified + * to the {@link DreamOverlayTouchMonitor}. + * + * This {@link DreamTouchHandler} is also responsible for fading in the complications at the end + * of the {@link com.android.systemui.dreams.touch.DreamTouchHandler.TouchSession}. + */ +public class HideComplicationTouchHandler implements DreamTouchHandler { + private static final String TAG = "HideComplicationHandler"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Complication.VisibilityController mVisibilityController; + private final int mRestoreTimeout; + private final Handler mHandler; + private final Executor mExecutor; + private final TouchInsetManager mTouchInsetManager; + + private final Runnable mRestoreComplications = new Runnable() { + @Override + public void run() { + mVisibilityController.setVisibility(View.VISIBLE, true); + } + }; + + @Inject + HideComplicationTouchHandler(Complication.VisibilityController visibilityController, + @Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout, + TouchInsetManager touchInsetManager, + @Main Executor executor, + @Main Handler handler) { + mVisibilityController = visibilityController; + mRestoreTimeout = restoreTimeout; + mHandler = handler; + mTouchInsetManager = touchInsetManager; + mExecutor = executor; + } + + @Override + public void onSessionStart(TouchSession session) { + if (DEBUG) { + Log.d(TAG, "onSessionStart"); + } + + // If other sessions are interested in this touch, do not fade out elements. + if (session.getActiveSessionCount() > 1) { + if (DEBUG) { + Log.d(TAG, "multiple active touch sessions, not fading"); + } + session.pop(); + return; + } + + session.registerInputListener(ev -> { + if (!(ev instanceof MotionEvent)) { + return; + } + + final MotionEvent motionEvent = (MotionEvent) ev; + + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { + if (DEBUG) { + Log.d(TAG, "ACTION_DOWN received"); + } + + final ListenableFuture<Boolean> touchCheck = mTouchInsetManager + .checkWithinTouchRegion(Math.round(motionEvent.getX()), + Math.round(motionEvent.getY())); + + touchCheck.addListener(() -> { + try { + if (!touchCheck.get()) { + mHandler.removeCallbacks(mRestoreComplications); + mVisibilityController.setVisibility(View.INVISIBLE, true); + } else { + // If a touch occurred inside the dream overlay touch insets, do not + // handle the touch. + session.pop(); + } + } catch (InterruptedException | ExecutionException exception) { + Log.e(TAG, "could not check TouchInsetManager:" + exception); + } + }, mExecutor); + } else if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL + || motionEvent.getAction() == MotionEvent.ACTION_UP) { + // End session and initiate delayed reappearance of the complications. + session.pop(); + mHandler.postDelayed(mRestoreComplications, mRestoreTimeout); + } + }); + } + + @Override + public void onSessionEnd(TouchSession session) { + if (DEBUG) { + Log.d(TAG, "onSessionEnd"); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java index dad0004613f6..7338ecba8cf3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java @@ -23,6 +23,7 @@ import dagger.Module; */ @Module(includes = { BouncerSwipeModule.class, + HideComplicationModule.class, }, subcomponents = { InputSessionComponent.class, }) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/HideComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/HideComplicationModule.java new file mode 100644 index 000000000000..3800ff75aed0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/HideComplicationModule.java @@ -0,0 +1,40 @@ +/* + * 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.dreams.touch.dagger; + +import com.android.systemui.dreams.touch.DreamTouchHandler; +import com.android.systemui.dreams.touch.HideComplicationTouchHandler; + +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; + +/** + * Module for {@link HideComplicationTouchHandler}. + */ +@Module +public class HideComplicationModule { + /** + * Provides {@link HideComplicationTouchHandler} for inclusion in touch handling over the dream. + */ + @Provides + @IntoSet + public static DreamTouchHandler providesHideComplicationTouchHandler( + HideComplicationTouchHandler touchHandler) { + return touchHandler; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java index de4e1e2f5886..3d07491283c5 100644 --- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java +++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java @@ -21,6 +21,10 @@ import android.graphics.Region; import android.view.View; import android.view.ViewRootImpl; +import androidx.concurrent.futures.CallbackToFutureAdapter; + +import com.google.common.util.concurrent.ListenableFuture; + import java.util.HashMap; import java.util.HashSet; import java.util.concurrent.Executor; @@ -141,6 +145,18 @@ public class TouchInsetManager { return new TouchInsetSession(this, mExecutor); } + /** + * Checks to see if the given point coordinates fall within an inset region. + */ + public ListenableFuture<Boolean> checkWithinTouchRegion(int x, int y) { + return CallbackToFutureAdapter.getFuture(completer -> { + mExecutor.execute(() -> completer.set( + mDefinedRegions.values().stream().anyMatch(region -> region.contains(x, y)))); + + return "DreamOverlayTouchMonitor::checkWithinTouchRegion"; + }); + } + private void updateTouchInset() { final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java index 51dcf2ec18f4..d1d9ec3af711 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java @@ -117,7 +117,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { mLayout); final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession); + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); addComplication(engine, firstViewInfo); // Ensure the view is added to the top end corner @@ -145,7 +145,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { mLayout); final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession); + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); addComplication(engine, firstViewInfo); // Ensure the view is added to the top end corner @@ -162,7 +162,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Test public void testDirectionLayout() { final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession); + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -211,7 +211,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Test public void testPositionLayout() { final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession); + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -299,7 +299,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { public void testMargin() { final int margin = 5; final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, margin, mTouchSession); + new ComplicationLayoutEngine(mLayout, margin, mTouchSession, 0, 0); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -374,7 +374,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Test public void testRemoval() { final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession); + new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java new file mode 100644 index 000000000000..0a021331d9d1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2022 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.dreams.touch; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.view.MotionEvent; +import android.view.View; + +import androidx.concurrent.futures.CallbackToFutureAdapter; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.complication.Complication; +import com.android.systemui.shared.system.InputChannelCompat; +import com.android.systemui.touch.TouchInsetManager; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class HideComplicationTouchHandlerTest extends SysuiTestCase { + private static final int RESTORE_TIMEOUT = 1000; + + @Mock + Complication.VisibilityController mVisibilityController; + + @Mock + TouchInsetManager mTouchInsetManager; + + @Mock + Handler mHandler; + + @Mock + MotionEvent mMotionEvent; + + @Mock + DreamTouchHandler.TouchSession mSession; + + FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensures no actions are taken when there multiple sessions. + */ + @Test + public void testSessionEndOnMultipleSessions() { + final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler( + mVisibilityController, + RESTORE_TIMEOUT, + mTouchInsetManager, + mFakeExecutor, + mHandler); + + // Report multiple active sessions. + when(mSession.getActiveSessionCount()).thenReturn(2); + + // Start session. + touchHandler.onSessionStart(mSession); + + // Verify session end. + verify(mSession).pop(); + + // Verify no interaction with visibility controller. + verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean()); + } + + /** + * Ensures no actions are taken when there multiple sessions. + */ + @Test + public void testSessionEndWithTouchInInset() { + final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler( + mVisibilityController, + RESTORE_TIMEOUT, + mTouchInsetManager, + mFakeExecutor, + mHandler); + + // Report one session + when(mSession.getActiveSessionCount()).thenReturn(1); + + // Start session + touchHandler.onSessionStart(mSession); + + // Capture input listener + final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor = + ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class); + verify(mSession).registerInputListener(inputEventListenerCaptor.capture()); + + // Report touch within the inset + when(mTouchInsetManager.checkWithinTouchRegion(anyInt(), anyInt())).thenReturn( + CallbackToFutureAdapter.getFuture(completer -> { + completer.set(true); + return "testSessionEndWithTouchInInset"; + }) + ); + + inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent); + mFakeExecutor.runAllReady(); + + // Verify session ended. + verify(mSession).pop(); + + // Verify no interaction with visibility controller. + verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean()); + } + + /** + * Make sure visibility changes are triggered from session events. + */ + @Test + public void testSessionLifecycle() { + final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler( + mVisibilityController, + RESTORE_TIMEOUT, + mTouchInsetManager, + mFakeExecutor, + mHandler); + + // Report one session + when(mSession.getActiveSessionCount()).thenReturn(1); + + // Start session + touchHandler.onSessionStart(mSession); + + // Capture input listener + final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor = + ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class); + verify(mSession).registerInputListener(inputEventListenerCaptor.capture()); + + // Report touch outside the inset + when(mTouchInsetManager.checkWithinTouchRegion(anyInt(), anyInt())).thenReturn( + CallbackToFutureAdapter.getFuture(completer -> { + completer.set(false); + return "testSessionEndWithTouchInInset"; + }) + ); + + // Send down event. + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); + inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent); + mFakeExecutor.runAllReady(); + + // Verify callback to restore visibility cancelled. + verify(mHandler).removeCallbacks(any()); + + // Verify visibility controller told to hide complications. + verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean()); + + Mockito.clearInvocations(mVisibilityController, mHandler); + + // Send up event. + when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP); + inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent); + mFakeExecutor.runAllReady(); + + // Verify visibility controller told to show complications. + ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mHandler).postDelayed(delayRunnableCaptor.capture(), + eq(Long.valueOf(RESTORE_TIMEOUT))); + delayRunnableCaptor.getValue().run(); + verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean()); + + // Verify session ended. + verify(mSession).pop(); + } +} |