Merge "TouchHandler for hiding dream overlay complications." into tm-dev
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -185,6 +185,19 @@ public interface Complication {
|
||||
void requestExitDream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import dagger.Module;
|
||||
*/
|
||||
@Module(includes = {
|
||||
BouncerSwipeModule.class,
|
||||
HideComplicationModule.class,
|
||||
}, subcomponents = {
|
||||
InputSessionComponent.class,
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user