Merge changes I81879e60,Ief02e781

* changes:
  Move Complication logic out of DreamOverlay.
  Do not specify root view in TouchInsetManager.
This commit is contained in:
Bryce Lee
2022-12-13 23:05:33 +00:00
committed by Android (Google) Code Review
10 changed files with 251 additions and 149 deletions

View File

@@ -45,7 +45,10 @@ import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.touch.TouchInsetManager;
import java.util.Arrays;
import java.util.HashSet;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -84,6 +87,9 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
private final ComplicationComponent mComplicationComponent;
private final com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
mDreamComplicationComponent;
private final DreamOverlayComponent mDreamOverlayComponent;
private final DreamOverlayLifecycleOwner mLifecycleOwner;
@@ -135,10 +141,13 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
DreamOverlayLifecycleOwner lifecycleOwner,
WindowManager windowManager,
ComplicationComponent.Factory complicationComponentFactory,
com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
dreamComplicationComponentFactory,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
UiEventLogger uiEventLogger,
TouchInsetManager touchInsetManager,
@Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
ComponentName lowLightDreamComponent) {
mContext = context;
@@ -154,9 +163,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
final Complication.Host host =
() -> mExecutor.execute(DreamOverlayService.this::requestExit);
mComplicationComponent = complicationComponentFactory.create();
mDreamOverlayComponent =
dreamOverlayComponentFactory.create(lifecycleOwner, viewModelStore, host, null);
mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
viewModelStore, touchInsetManager);
mDreamComplicationComponent = dreamComplicationComponentFactory.create(
mComplicationComponent.getVisibilityController(), touchInsetManager);
mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
new HashSet<>(Arrays.asList(
mDreamComplicationComponent.getHideComplicationTouchHandler())));
mLifecycleOwner = lifecycleOwner;
mLifecycleRegistry = mLifecycleOwner.getRegistry();

View File

@@ -30,7 +30,7 @@ import androidx.constraintlayout.widget.Constraints;
import com.android.systemui.R;
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.complication.dagger.ComplicationModule;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.touch.TouchInsetManager;
@@ -50,7 +50,7 @@ import javax.inject.Named;
* their layout parameters and attributes. The management of this set is done by
* {@link ComplicationHostViewController}.
*/
@DreamOverlayComponent.DreamOverlayScope
@ComplicationModule.ComplicationScope
public class ComplicationLayoutEngine implements Complication.VisibilityController {
public static final String TAG = "ComplicationLayoutEng";

View File

@@ -1,12 +1,29 @@
package com.android.systemui.dreams.complication.dagger
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStore
import com.android.systemui.dreams.complication.Complication
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutEngine
import com.android.systemui.touch.TouchInsetManager
import dagger.BindsInstance
import dagger.Subcomponent
@Subcomponent
@Subcomponent(modules = [ComplicationModule::class])
@ComplicationModule.ComplicationScope
interface ComplicationComponent {
/** Factory for generating [ComplicationComponent]. */
@Subcomponent.Factory
interface Factory {
fun create(): ComplicationComponent
fun create(
@BindsInstance lifecycleOwner: LifecycleOwner,
@BindsInstance host: Complication.Host,
@BindsInstance viewModelStore: ViewModelStore,
@BindsInstance touchInsetManager: TouchInsetManager
): ComplicationComponent
}
fun getComplicationHostViewController(): ComplicationHostViewController
fun getVisibilityController(): ComplicationLayoutEngine
}

View File

@@ -24,13 +24,12 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.util.Preconditions;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import javax.inject.Named;
import dagger.Module;
import dagger.Provides;
import javax.inject.Named;
/**
* Module for providing a scoped host view.
*/
@@ -49,7 +48,7 @@ public abstract class ComplicationHostViewModule {
*/
@Provides
@Named(SCOPED_COMPLICATIONS_LAYOUT)
@DreamOverlayComponent.DreamOverlayScope
@ComplicationModule.ComplicationScope
static ConstraintLayout providesComplicationHostView(
LayoutInflater layoutInflater) {
return Preconditions.checkNotNull((ConstraintLayout)
@@ -60,7 +59,6 @@ public abstract class ComplicationHostViewModule {
@Provides
@Named(COMPLICATION_MARGIN_DEFAULT)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationPadding(@Main Resources resources) {
return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
}
@@ -70,7 +68,6 @@ public abstract class ComplicationHostViewModule {
*/
@Provides
@Named(COMPLICATIONS_FADE_OUT_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsFadeOutDuration(@Main Resources resources) {
return resources.getInteger(R.integer.complicationFadeOutMs);
}
@@ -80,7 +77,6 @@ public abstract class ComplicationHostViewModule {
*/
@Provides
@Named(COMPLICATIONS_FADE_OUT_DELAY)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsFadeOutDelay(@Main Resources resources) {
return resources.getInteger(R.integer.complicationFadeOutDelayMs);
}
@@ -90,7 +86,6 @@ public abstract class ComplicationHostViewModule {
*/
@Provides
@Named(COMPLICATIONS_FADE_IN_DURATION)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsFadeInDuration(@Main Resources resources) {
return resources.getInteger(R.integer.complicationFadeInMs);
}
@@ -100,7 +95,6 @@ public abstract class ComplicationHostViewModule {
*/
@Provides
@Named(COMPLICATIONS_RESTORE_TIMEOUT)
@DreamOverlayComponent.DreamOverlayScope
static int providesComplicationsRestoreTimeout(@Main Resources resources) {
return resources.getInteger(R.integer.complicationRestoreMs);
}

View File

@@ -24,16 +24,16 @@ 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 com.android.systemui.touch.TouchInsetManager;
import dagger.Module;
import dagger.Provides;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import javax.inject.Named;
import javax.inject.Scope;
import dagger.Module;
import dagger.Provides;
/**
* Module for housing components related to rendering complications.
*/
@@ -73,4 +73,13 @@ public interface ComplicationModule {
ComplicationLayoutEngine engine) {
return engine;
}
/**
* Provides a new touch inset session instance for complication logic.
*/
@Provides
static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
TouchInsetManager manager) {
return manager.createSession();
}
}

View File

@@ -23,14 +23,13 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelStore;
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.complication.ComplicationHostViewController;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
import com.android.systemui.touch.TouchInsetManager;
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -48,7 +47,6 @@ import javax.inject.Scope;
@Subcomponent(modules = {
DreamTouchModule.class,
DreamOverlayModule.class,
ComplicationModule.class,
})
@DreamOverlayComponent.DreamOverlayScope
public interface DreamOverlayComponent {
@@ -57,8 +55,8 @@ public interface DreamOverlayComponent {
interface Factory {
DreamOverlayComponent create(
@BindsInstance LifecycleOwner lifecycleOwner,
@BindsInstance ViewModelStore store,
@BindsInstance Complication.Host host,
@BindsInstance ComplicationHostViewController complicationHostViewController,
@BindsInstance TouchInsetManager touchInsetManager,
@BindsInstance @Named(DREAM_TOUCH_HANDLERS) @Nullable
Set<DreamTouchHandler> dreamTouchHandlers);
}

View File

@@ -29,20 +29,15 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler;
import com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.touch.TouchInsetManager;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import dagger.multibindings.IntoSet;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Named;
@@ -106,14 +101,6 @@ public abstract class DreamOverlayModule {
return manager.createSession();
}
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
public static TouchInsetManager providesTouchInsetManager(@Main Executor executor,
DreamOverlayContainerView view) {
return new TouchInsetManager(executor, view);
}
/** */
@Provides
@DreamOverlayComponent.DreamOverlayScope
@@ -264,18 +251,4 @@ public abstract class DreamOverlayModule {
@Named(DREAM_TOUCH_HANDLERS) @Nullable Set<DreamTouchHandler> touchHandlers) {
return touchHandlers != null ? touchHandlers : new HashSet<>();
}
/**
* Provides {@link HideComplicationTouchHandler} for inclusion in touch handling over the dream.
*/
@Provides
@IntoSet
public static DreamTouchHandler providesHideComplicationTouchHandler(
ComplicationComponent.Factory componentFactory,
Complication.VisibilityController visibilityController,
TouchInsetManager touchInsetManager) {
ComplicationComponent component =
componentFactory.create(visibilityController, touchInsetManager);
return component.getHideComplicationTouchHandler();
}
}

View File

@@ -18,41 +18,58 @@ package com.android.systemui.touch;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.Log;
import android.view.AttachedSurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import com.android.systemui.dagger.qualifiers.Main;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This
* is useful for passing through touch events for all but select areas.
*/
public class TouchInsetManager {
private static final String TAG = "TouchInsetManager";
/**
* {@link TouchInsetSession} provides an individualized session with the
* {@link TouchInsetManager}, linking any action to the client.
*/
public static class TouchInsetSession {
private final TouchInsetManager mManager;
private final HashSet<View> mTrackedViews;
private final Executor mExecutor;
private final View.OnLayoutChangeListener mOnLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
-> updateTouchRegion();
-> updateTouchRegions();
private final View.OnAttachStateChangeListener mAttachListener =
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
updateTouchRegions();
}
@Override
public void onViewDetachedFromWindow(View v) {
updateTouchRegions();
}
};
/**
* Default constructor
* @param manager The parent {@link TouchInsetManager} which will be affected by actions on
* this session.
* @param rootView The parent of views that will be tracked.
* @param executor An executor for marshalling operations.
*/
TouchInsetSession(TouchInsetManager manager, Executor executor) {
@@ -68,8 +85,9 @@ public class TouchInsetManager {
public void addViewToTracking(View view) {
mExecutor.execute(() -> {
mTrackedViews.add(view);
view.addOnAttachStateChangeListener(mAttachListener);
view.addOnLayoutChangeListener(mOnLayoutChangeListener);
updateTouchRegion();
updateTouchRegions();
});
}
@@ -81,22 +99,30 @@ public class TouchInsetManager {
mExecutor.execute(() -> {
mTrackedViews.remove(view);
view.removeOnLayoutChangeListener(mOnLayoutChangeListener);
updateTouchRegion();
view.removeOnAttachStateChangeListener(mAttachListener);
updateTouchRegions();
});
}
private void updateTouchRegion() {
final Region cumulativeRegion = Region.obtain();
private void updateTouchRegions() {
mExecutor.execute(() -> {
final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
mTrackedViews.stream().forEach(view -> {
if (!view.isAttachedToWindow()) {
return;
}
mTrackedViews.stream().forEach(view -> {
final Rect boundaries = new Rect();
view.getBoundsOnScreen(boundaries);
cumulativeRegion.op(boundaries, Region.Op.UNION);
final AttachedSurfaceControl surface = view.getRootSurfaceControl();
if (!affectedSurfaces.containsKey(surface)) {
affectedSurfaces.put(surface, Region.obtain());
}
final Rect boundaries = new Rect();
view.getBoundsOnScreen(boundaries);
affectedSurfaces.get(surface).op(boundaries, Region.Op.UNION);
});
mManager.setTouchRegions(this, affectedSurfaces);
});
mManager.setTouchRegion(this, cumulativeRegion);
cumulativeRegion.recycle();
}
/**
@@ -110,32 +136,18 @@ public class TouchInsetManager {
}
}
private final HashMap<TouchInsetSession, Region> mDefinedRegions = new HashMap<>();
private final HashMap<TouchInsetSession, HashMap<AttachedSurfaceControl, Region>>
mSessionRegions = new HashMap<>();
private final HashMap<AttachedSurfaceControl, Region> mLastAffectedSurfaces = new HashMap();
private final Executor mExecutor;
private final View mRootView;
private final View.OnAttachStateChangeListener mAttachListener =
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
updateTouchInset();
}
@Override
public void onViewDetachedFromWindow(View v) {
}
};
/**
* Default constructor.
* @param executor An {@link Executor} to marshal all operations on.
* @param rootView The root {@link View} for all views in sessions.
*/
public TouchInsetManager(Executor executor, View rootView) {
@Inject
public TouchInsetManager(@Main Executor executor) {
mExecutor = executor;
mRootView = rootView;
mRootView.addOnAttachStateChangeListener(mAttachListener);
}
/**
@@ -151,47 +163,68 @@ public class TouchInsetManager {
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))));
mLastAffectedSurfaces.values().stream().anyMatch(
region -> region.contains(x, y))));
return "DreamOverlayTouchMonitor::checkWithinTouchRegion";
});
}
private void updateTouchInset() {
final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl();
private void updateTouchInsets() {
// Get affected
final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
mSessionRegions.values().stream().forEach(regionMapping -> {
regionMapping.entrySet().stream().forEach(entry -> {
final AttachedSurfaceControl surface = entry.getKey();
if (!affectedSurfaces.containsKey(surface)) {
affectedSurfaces.put(surface, Region.obtain());
}
if (viewRootImpl == null) {
affectedSurfaces.get(surface).op(entry.getValue(), Region.Op.UNION);
});
});
affectedSurfaces.entrySet().stream().forEach(entry -> {
entry.getKey().setTouchableRegion(entry.getValue());
});
mLastAffectedSurfaces.entrySet().forEach(entry -> {
final AttachedSurfaceControl surface = entry.getKey();
if (!affectedSurfaces.containsKey(surface)) {
surface.setTouchableRegion(null);
}
entry.getValue().recycle();
});
mLastAffectedSurfaces.clear();
mLastAffectedSurfaces.putAll(affectedSurfaces);
}
protected void setTouchRegions(TouchInsetSession session,
HashMap<AttachedSurfaceControl, Region> regions) {
mExecutor.execute(() -> {
recycleRegions(session);
mSessionRegions.put(session, regions);
updateTouchInsets();
});
}
private void recycleRegions(TouchInsetSession session) {
if (!mSessionRegions.containsKey(session)) {
Log.w(TAG, "Removing a session with no regions:" + session);
return;
}
final Region aggregateRegion = Region.obtain();
for (Region region : mDefinedRegions.values()) {
aggregateRegion.op(region, Region.Op.UNION);
for (Region region : mSessionRegions.get(session).values()) {
region.recycle();
}
viewRootImpl.setTouchableRegion(aggregateRegion);
aggregateRegion.recycle();
}
protected void setTouchRegion(TouchInsetSession session, Region region) {
final Region introducedRegion = Region.obtain(region);
mExecutor.execute(() -> {
mDefinedRegions.put(session, introducedRegion);
updateTouchInset();
});
}
private void clearRegion(TouchInsetSession session) {
mExecutor.execute(() -> {
final Region storedRegion = mDefinedRegions.remove(session);
if (storedRegion != null) {
storedRegion.recycle();
}
updateTouchInset();
recycleRegions(session);
mSessionRegions.remove(session);
updateTouchInsets();
});
}
}

View File

@@ -19,7 +19,6 @@ package com.android.systemui.dreams;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -45,9 +44,12 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationLayoutEngine;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler;
import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -93,6 +95,20 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Mock
ComplicationComponent mComplicationComponent;
@Mock
ComplicationLayoutEngine mComplicationVisibilityController;
@Mock
com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
mDreamComplicationComponentFactory;
@Mock
com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
mDreamComplicationComponent;
@Mock
HideComplicationTouchHandler mHideComplicationTouchHandler;
@Mock
DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
@@ -117,6 +133,9 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
@Mock
ViewGroup mDreamOverlayContainerViewParent;
@Mock
TouchInsetManager mTouchInsetManager;
@Mock
UiEventLogger mUiEventLogger;
@@ -136,25 +155,33 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
.thenReturn(mDreamOverlayTouchMonitor);
when(mComplicationComponentFactory
.create())
.create(any(), any(), any(), any()))
.thenReturn(mComplicationComponent);
// TODO(b/261781069): A touch handler should be passed in from the complication component
// when the complication component is introduced.
when(mComplicationComponent.getVisibilityController())
.thenReturn(mComplicationVisibilityController);
when(mDreamComplicationComponent.getHideComplicationTouchHandler())
.thenReturn(mHideComplicationTouchHandler);
when(mDreamComplicationComponentFactory
.create(any(), any()))
.thenReturn(mDreamComplicationComponent);
when(mDreamOverlayComponentFactory
.create(any(), any(), any(), isNull()))
.create(any(), any(), any(), any()))
.thenReturn(mDreamOverlayComponent);
when(mDreamOverlayContainerViewController.getContainerView())
.thenReturn(mDreamOverlayContainerView);
mService = new DreamOverlayService(mContext,
mService = new DreamOverlayService(
mContext,
mMainExecutor,
mLifecycleOwner,
mWindowManager,
mComplicationComponentFactory,
mDreamComplicationComponentFactory,
mDreamOverlayComponentFactory,
mStateController,
mKeyguardUpdateMonitor,
mUiEventLogger,
mTouchInsetManager,
LOW_LIGHT_COMPONENT);
}

View File

@@ -26,8 +26,8 @@ import static org.mockito.Mockito.when;
import android.graphics.Rect;
import android.graphics.Region;
import android.testing.AndroidTestingRunner;
import android.view.AttachedSurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
import androidx.test.filters.SmallTest;
@@ -47,41 +47,78 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class TouchInsetManagerTest extends SysuiTestCase {
@Mock
private View mRootView;
@Mock
private ViewRootImpl mRootViewImpl;
private AttachedSurfaceControl mAttachedSurfaceControl;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mRootView.getViewRootImpl()).thenReturn(mRootViewImpl);
}
@Test
public void testRootViewOnAttachedHandling() {
public void testViewOnAttachedHandling() {
// Create inset manager
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
mRootView);
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
final View view = createView(new Rect(0, 0, 0, 0));
when(view.isAttachedToWindow()).thenReturn(false);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
session.addViewToTracking(view);
mFakeExecutor.runAllReady();
// Ensure manager has registered to listen to attached state of root view.
verify(mRootView).addOnAttachStateChangeListener(listener.capture());
verify(view).addOnAttachStateChangeListener(listener.capture());
clearInvocations(mAttachedSurfaceControl);
when(view.isAttachedToWindow()).thenReturn(true);
// Trigger attachment and verify touchable region is set.
listener.getValue().onViewAttachedToWindow(mRootView);
verify(mRootViewImpl).setTouchableRegion(any());
listener.getValue().onViewAttachedToWindow(view);
mFakeExecutor.runAllReady();
verify(mAttachedSurfaceControl).setTouchableRegion(any());
}
@Test
public void testViewOnDetachedHandling() {
// Create inset manager
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
final View view = createView(new Rect(0, 0, 0, 0));
when(view.isAttachedToWindow()).thenReturn(true);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
session.addViewToTracking(view);
mFakeExecutor.runAllReady();
// Ensure manager has registered to listen to attached state of root view.
verify(view).addOnAttachStateChangeListener(listener.capture());
clearInvocations(mAttachedSurfaceControl);
when(view.isAttachedToWindow()).thenReturn(false);
// Trigger detachment and verify touchable region is set.
listener.getValue().onViewDetachedFromWindow(view);
mFakeExecutor.runAllReady();
verify(mAttachedSurfaceControl).setTouchableRegion(any());
}
@Test
public void testInsetRegionPropagation() {
// Create inset manager
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
mRootView);
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -95,14 +132,13 @@ public class TouchInsetManagerTest extends SysuiTestCase {
// Check to see if view was properly accounted for.
final Region expectedRegion = Region.obtain();
expectedRegion.op(rect, Region.Op.UNION);
verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
@Test
public void testMultipleRegions() {
// Create inset manager
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
mRootView);
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -112,7 +148,7 @@ public class TouchInsetManagerTest extends SysuiTestCase {
session.addViewToTracking(createView(firstBounds));
mFakeExecutor.runAllReady();
clearInvocations(mRootViewImpl);
clearInvocations(mAttachedSurfaceControl);
// Create second session
final TouchInsetManager.TouchInsetSession secondSession = insetManager.createSession();
@@ -128,27 +164,26 @@ public class TouchInsetManagerTest extends SysuiTestCase {
final Region expectedRegion = Region.obtain();
expectedRegion.op(firstBounds, Region.Op.UNION);
expectedRegion.op(secondBounds, Region.Op.UNION);
verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
clearInvocations(mRootViewImpl);
clearInvocations(mAttachedSurfaceControl);
// clear first session, ensure second session is still reflected.
session.clear();
mFakeExecutor.runAllReady();
{
final Region expectedRegion = Region.obtain();
expectedRegion.op(firstBounds, Region.Op.UNION);
verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
expectedRegion.op(secondBounds, Region.Op.UNION);
verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
}
@Test
public void testMultipleViews() {
// Create inset manager
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
mRootView);
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
// Create session
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -159,7 +194,7 @@ public class TouchInsetManagerTest extends SysuiTestCase {
// only capture second invocation.
mFakeExecutor.runAllReady();
clearInvocations(mRootViewImpl);
clearInvocations(mAttachedSurfaceControl);
// Add a second view to the session
final Rect secondViewBounds = new Rect(4, 4, 9, 10);
@@ -173,20 +208,20 @@ public class TouchInsetManagerTest extends SysuiTestCase {
final Region expectedRegion = Region.obtain();
expectedRegion.op(firstViewBounds, Region.Op.UNION);
expectedRegion.op(secondViewBounds, Region.Op.UNION);
verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
// Remove second view.
session.removeViewFromTracking(secondView);
clearInvocations(mRootViewImpl);
clearInvocations(mAttachedSurfaceControl);
mFakeExecutor.runAllReady();
// Ensure first view still reflected in touch region.
{
final Region expectedRegion = Region.obtain();
expectedRegion.op(firstViewBounds, Region.Op.UNION);
verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
}
}
@@ -197,6 +232,8 @@ public class TouchInsetManagerTest extends SysuiTestCase {
((Rect) invocation.getArgument(0)).set(rect);
return null;
}).when(view).getBoundsOnScreen(any());
when(view.isAttachedToWindow()).thenReturn(true);
when(view.getRootSurfaceControl()).thenReturn(mAttachedSurfaceControl);
return view;
}