TouchInsetManager Introduction.
This changelist introduces TouchInsetManager to handle touchable insets across different locations within the same root view. Bug: 214292772 Test: atest TouchInsetManagerTest Change-Id: I9fa50d47440a130ffd8ed36fd9bc1e4f42ef303c
This commit is contained in:
@@ -18,12 +18,9 @@ package com.android.systemui.dreams;
|
||||
|
||||
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.R;
|
||||
@@ -62,43 +59,6 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
// Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
|
||||
private final Handler mHandler;
|
||||
|
||||
// A hook into the internal inset calculation where we declare the overlays as the only
|
||||
// touchable regions.
|
||||
private final ViewTreeObserver.OnComputeInternalInsetsListener
|
||||
mOnComputeInternalInsetsListener =
|
||||
new ViewTreeObserver.OnComputeInternalInsetsListener() {
|
||||
@Override
|
||||
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
|
||||
inoutInfo.setTouchableInsets(
|
||||
ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
|
||||
final Region region = new Region();
|
||||
final Rect rect = new Rect();
|
||||
final int childCount = mDreamOverlayContentView.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = mDreamOverlayContentView.getChildAt(i);
|
||||
|
||||
if (mComplicationHostViewController.getView() == child) {
|
||||
region.op(mComplicationHostViewController.getTouchRegions(),
|
||||
Region.Op.UNION);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.getGlobalVisibleRect(rect)) {
|
||||
region.op(rect, Region.Op.UNION);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the notifications drag area to the tap region (otherwise the
|
||||
// notifications shade can't be dragged down).
|
||||
if (mDreamOverlayContentView.getGlobalVisibleRect(rect)) {
|
||||
rect.bottom = rect.top + mDreamOverlayNotificationsDragAreaHeight;
|
||||
region.op(rect, Region.Op.UNION);
|
||||
}
|
||||
|
||||
inoutInfo.touchableRegion.set(region);
|
||||
}
|
||||
};
|
||||
|
||||
@Inject
|
||||
public DreamOverlayContainerViewController(
|
||||
DreamOverlayContainerView containerView,
|
||||
@@ -136,16 +96,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
|
||||
@Override
|
||||
protected void onViewAttached() {
|
||||
mView.getViewTreeObserver()
|
||||
.addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
|
||||
mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewDetached() {
|
||||
mHandler.removeCallbacks(this::updateBurnInOffsets);
|
||||
mView.getViewTreeObserver()
|
||||
.removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
|
||||
}
|
||||
|
||||
View getContainerView() {
|
||||
@@ -162,6 +118,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
|
||||
// so no translation occurs when the values don't change.
|
||||
mView.setTranslationX(getBurnInOffset(mMaxBurnInOffset * 2, true)
|
||||
- mMaxBurnInOffset);
|
||||
|
||||
mView.setTranslationY(getBurnInOffset(mMaxBurnInOffset * 2, false)
|
||||
- mMaxBurnInOffset);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
|
||||
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
|
||||
import com.android.systemui.touch.TouchInsetManager;
|
||||
import com.android.systemui.util.ViewController;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -48,6 +49,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
|
||||
private static final int WIFI_STATUS_AVAILABLE = 2;
|
||||
|
||||
private final ConnectivityManager mConnectivityManager;
|
||||
private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
|
||||
|
||||
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
|
||||
.clearCapabilities()
|
||||
@@ -77,9 +79,11 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
|
||||
@Inject
|
||||
public DreamOverlayStatusBarViewController(
|
||||
DreamOverlayStatusBarView view,
|
||||
ConnectivityManager connectivityManager) {
|
||||
ConnectivityManager connectivityManager,
|
||||
TouchInsetManager.TouchInsetSession touchInsetSession) {
|
||||
super(view);
|
||||
mConnectivityManager = connectivityManager;
|
||||
mTouchInsetSession = touchInsetSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,11 +96,13 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
|
||||
onWifiAvailabilityChanged(
|
||||
capabilities != null
|
||||
&& capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
|
||||
mTouchInsetSession.addViewToTracking(mView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewDetached() {
|
||||
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
|
||||
mTouchInsetSession.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.Constraints;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.touch.TouchInsetManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -52,6 +53,7 @@ public class ComplicationLayoutEngine {
|
||||
private static class ViewEntry implements Comparable<ViewEntry> {
|
||||
private final View mView;
|
||||
private final ComplicationLayoutParams mLayoutParams;
|
||||
private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
|
||||
private final Parent mParent;
|
||||
@Complication.Category
|
||||
private final int mCategory;
|
||||
@@ -61,7 +63,8 @@ public class ComplicationLayoutEngine {
|
||||
* Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
|
||||
* view hierarchy to be accessed without traversing the entire view tree.
|
||||
*/
|
||||
ViewEntry(View view, ComplicationLayoutParams layoutParams, int category, Parent parent,
|
||||
ViewEntry(View view, ComplicationLayoutParams layoutParams,
|
||||
TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent,
|
||||
int margin) {
|
||||
mView = view;
|
||||
// Views that are generated programmatically do not have a unique id assigned to them
|
||||
@@ -70,9 +73,12 @@ public class ComplicationLayoutEngine {
|
||||
// {@link Complication.ViewHolder} should not reference the root container by id.
|
||||
mView.setId(View.generateViewId());
|
||||
mLayoutParams = layoutParams;
|
||||
mTouchInsetSession = touchSession;
|
||||
mCategory = category;
|
||||
mParent = parent;
|
||||
mMargin = margin;
|
||||
|
||||
touchSession.addViewToTracking(mView);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,6 +223,7 @@ public class ComplicationLayoutEngine {
|
||||
mParent.removeEntry(this);
|
||||
|
||||
((ViewGroup) mView.getParent()).removeView(mView);
|
||||
mTouchInsetSession.removeViewFromTracking(mView);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -242,15 +249,18 @@ public class ComplicationLayoutEngine {
|
||||
*/
|
||||
private static class Builder {
|
||||
private final View mView;
|
||||
private final TouchInsetManager.TouchInsetSession mTouchSession;
|
||||
private final ComplicationLayoutParams mLayoutParams;
|
||||
private final int mCategory;
|
||||
private Parent mParent;
|
||||
private int mMargin;
|
||||
|
||||
Builder(View view, ComplicationLayoutParams lp, @Complication.Category int category) {
|
||||
Builder(View view, TouchInsetManager.TouchInsetSession touchSession,
|
||||
ComplicationLayoutParams lp, @Complication.Category int category) {
|
||||
mView = view;
|
||||
mLayoutParams = lp;
|
||||
mCategory = category;
|
||||
mTouchSession = touchSession;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +301,8 @@ public class ComplicationLayoutEngine {
|
||||
* Builds and returns the resulting {@link ViewEntry}.
|
||||
*/
|
||||
ViewEntry build() {
|
||||
return new ViewEntry(mView, mLayoutParams, mCategory, mParent, mMargin);
|
||||
return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent,
|
||||
mMargin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,13 +453,16 @@ public class ComplicationLayoutEngine {
|
||||
private final int mMargin;
|
||||
private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
|
||||
private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
|
||||
private final TouchInsetManager.TouchInsetSession mSession;
|
||||
|
||||
/** */
|
||||
@Inject
|
||||
public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout,
|
||||
@Named(COMPLICATION_MARGIN) int margin) {
|
||||
@Named(COMPLICATION_MARGIN) int margin,
|
||||
TouchInsetManager.TouchInsetSession session) {
|
||||
mLayout = layout;
|
||||
mMargin = margin;
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,7 +482,7 @@ public class ComplicationLayoutEngine {
|
||||
removeComplication(id);
|
||||
}
|
||||
|
||||
final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, lp, category)
|
||||
final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category)
|
||||
.setMargin(mMargin);
|
||||
|
||||
// Add position group if doesn't already exist
|
||||
|
||||
@@ -29,6 +29,9 @@ 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.touch.TouchInsetManager;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
@@ -63,6 +66,21 @@ public abstract class DreamOverlayModule {
|
||||
"R.id.dream_overlay_content must not be null");
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
public static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
|
||||
TouchInsetManager manager) {
|
||||
return manager.createSession();
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@DreamOverlayComponent.DreamOverlayScope
|
||||
public static TouchInsetManager providesTouchInsetManager(@Main Executor executor,
|
||||
DreamOverlayContainerView view) {
|
||||
return new TouchInsetManager(executor, view);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Provides
|
||||
@DreamOverlayComponent.DreamOverlayScope
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.touch;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.view.View;
|
||||
import android.view.ViewRootImpl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* {@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 {
|
||||
/**
|
||||
* {@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();
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
mManager = manager;
|
||||
mTrackedViews = new HashSet<>();
|
||||
mExecutor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a descendant of the root view to be tracked.
|
||||
* @param view {@link View} to be tracked.
|
||||
*/
|
||||
public void addViewToTracking(View view) {
|
||||
mExecutor.execute(() -> {
|
||||
mTrackedViews.add(view);
|
||||
view.addOnLayoutChangeListener(mOnLayoutChangeListener);
|
||||
updateTouchRegion();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a view from further tracking
|
||||
* @param view {@link View} to be removed.
|
||||
*/
|
||||
public void removeViewFromTracking(View view) {
|
||||
mExecutor.execute(() -> {
|
||||
mTrackedViews.remove(view);
|
||||
view.removeOnLayoutChangeListener(mOnLayoutChangeListener);
|
||||
updateTouchRegion();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateTouchRegion() {
|
||||
final Region cumulativeRegion = Region.obtain();
|
||||
|
||||
mTrackedViews.stream().forEach(view -> {
|
||||
final Rect boundaries = new Rect();
|
||||
view.getBoundsOnScreen(boundaries);
|
||||
cumulativeRegion.op(boundaries, Region.Op.UNION);
|
||||
});
|
||||
|
||||
mManager.setTouchRegion(this, cumulativeRegion);
|
||||
|
||||
cumulativeRegion.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all tracked views and updates insets accordingly.
|
||||
*/
|
||||
public void clear() {
|
||||
mExecutor.execute(() -> {
|
||||
mManager.clearRegion(this);
|
||||
mTrackedViews.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final HashMap<TouchInsetSession, Region> mDefinedRegions = 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) {
|
||||
mExecutor = executor;
|
||||
mRootView = rootView;
|
||||
mRootView.addOnAttachStateChangeListener(mAttachListener);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new associated session.
|
||||
*/
|
||||
public TouchInsetSession createSession() {
|
||||
return new TouchInsetSession(this, mExecutor);
|
||||
}
|
||||
|
||||
private void updateTouchInset() {
|
||||
final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl();
|
||||
|
||||
if (viewRootImpl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Region aggregateRegion = Region.obtain();
|
||||
|
||||
for (Region region : mDefinedRegions.values()) {
|
||||
aggregateRegion.op(region, Region.Op.UNION);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -117,31 +116,6 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
|
||||
DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnViewAttachedRegistersComputeInsetsListener() {
|
||||
mController.onViewAttached();
|
||||
verify(mViewTreeObserver).addOnComputeInternalInsetsListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnViewDetachedUnregistersComputeInsetsListener() {
|
||||
mController.onViewDetached();
|
||||
verify(mViewTreeObserver).removeOnComputeInternalInsetsListener(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeInsetsListenerReturnsRegion() {
|
||||
final ArgumentCaptor<ViewTreeObserver.OnComputeInternalInsetsListener>
|
||||
computeInsetsListenerCapture =
|
||||
ArgumentCaptor.forClass(ViewTreeObserver.OnComputeInternalInsetsListener.class);
|
||||
mController.onViewAttached();
|
||||
verify(mViewTreeObserver).addOnComputeInternalInsetsListener(
|
||||
computeInsetsListenerCapture.capture());
|
||||
final ViewTreeObserver.InternalInsetsInfo info = new ViewTreeObserver.InternalInsetsInfo();
|
||||
computeInsetsListenerCapture.getValue().onComputeInternalInsets(info);
|
||||
assertNotNull(info.touchableRegion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBurnInProtectionStartsWhenContentViewAttached() {
|
||||
mController.onViewAttached();
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.testing.AndroidTestingRunner;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.touch.TouchInsetManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -49,13 +50,16 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
|
||||
NetworkCapabilities mNetworkCapabilities;
|
||||
@Mock
|
||||
Network mNetwork;
|
||||
@Mock
|
||||
TouchInsetManager.TouchInsetSession mTouchSession;
|
||||
|
||||
DreamOverlayStatusBarViewController mController;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mController = new DreamOverlayStatusBarViewController(mView, mConnectivityManager);
|
||||
mController = new DreamOverlayStatusBarViewController(mView, mConnectivityManager,
|
||||
mTouchSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.touch.TouchInsetManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -46,6 +47,9 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
@Mock
|
||||
ConstraintLayout mLayout;
|
||||
|
||||
@Mock
|
||||
TouchInsetManager.TouchInsetSession mTouchSession;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
@@ -112,7 +116,8 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
Complication.CATEGORY_STANDARD,
|
||||
mLayout);
|
||||
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
|
||||
final ComplicationLayoutEngine engine =
|
||||
new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
|
||||
addComplication(engine, firstViewInfo);
|
||||
|
||||
// Ensure the view is added to the top end corner
|
||||
@@ -139,7 +144,8 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
Complication.CATEGORY_STANDARD,
|
||||
mLayout);
|
||||
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
|
||||
final ComplicationLayoutEngine engine =
|
||||
new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
|
||||
addComplication(engine, firstViewInfo);
|
||||
|
||||
// Ensure the view is added to the top end corner
|
||||
@@ -155,7 +161,8 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
*/
|
||||
@Test
|
||||
public void testDirectionLayout() {
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
|
||||
final ComplicationLayoutEngine engine =
|
||||
new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
|
||||
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
@@ -203,7 +210,8 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
*/
|
||||
@Test
|
||||
public void testPositionLayout() {
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
|
||||
final ComplicationLayoutEngine engine =
|
||||
new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
|
||||
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
@@ -290,7 +298,8 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testMargin() {
|
||||
final int margin = 5;
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, margin);
|
||||
final ComplicationLayoutEngine engine =
|
||||
new ComplicationLayoutEngine(mLayout, margin, mTouchSession);
|
||||
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
@@ -364,7 +373,8 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase {
|
||||
*/
|
||||
@Test
|
||||
public void testRemoval() {
|
||||
final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, 0);
|
||||
final ComplicationLayoutEngine engine =
|
||||
new ComplicationLayoutEngine(mLayout, 0, mTouchSession);
|
||||
|
||||
final ViewInfo firstViewInfo = new ViewInfo(
|
||||
new ComplicationLayoutParams(
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.touch;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.View;
|
||||
import android.view.ViewRootImpl;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
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 TouchInsetManagerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
private View mRootView;
|
||||
|
||||
@Mock
|
||||
private ViewRootImpl mRootViewImpl;
|
||||
|
||||
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mRootView.getViewRootImpl()).thenReturn(mRootViewImpl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRootViewOnAttachedHandling() {
|
||||
// Create inset manager
|
||||
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
|
||||
mRootView);
|
||||
|
||||
final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
|
||||
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
|
||||
|
||||
// Ensure manager has registered to listen to attached state of root view.
|
||||
verify(mRootView).addOnAttachStateChangeListener(listener.capture());
|
||||
|
||||
// Trigger attachment and verify touchable region is set.
|
||||
listener.getValue().onViewAttachedToWindow(mRootView);
|
||||
verify(mRootViewImpl).setTouchableRegion(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsetRegionPropagation() {
|
||||
// Create inset manager
|
||||
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
|
||||
mRootView);
|
||||
|
||||
// Create session
|
||||
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
|
||||
|
||||
// Add a view to the session.
|
||||
final Rect rect = new Rect(0, 0, 2, 2);
|
||||
|
||||
session.addViewToTracking(createView(rect));
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleRegions() {
|
||||
// Create inset manager
|
||||
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
|
||||
mRootView);
|
||||
|
||||
// Create session
|
||||
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
|
||||
|
||||
// Add a view to the session.
|
||||
final Rect firstBounds = new Rect(0, 0, 2, 2);
|
||||
session.addViewToTracking(createView(firstBounds));
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
clearInvocations(mRootViewImpl);
|
||||
|
||||
// Create second session
|
||||
final TouchInsetManager.TouchInsetSession secondSession = insetManager.createSession();
|
||||
|
||||
// Add a view to the second session.
|
||||
final Rect secondBounds = new Rect(4, 4, 8, 10);
|
||||
secondSession.addViewToTracking(createView(secondBounds));
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
// Check to see if all views and sessions was properly accounted for.
|
||||
{
|
||||
final Region expectedRegion = Region.obtain();
|
||||
expectedRegion.op(firstBounds, Region.Op.UNION);
|
||||
expectedRegion.op(secondBounds, Region.Op.UNION);
|
||||
verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
|
||||
}
|
||||
|
||||
|
||||
clearInvocations(mRootViewImpl);
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleViews() {
|
||||
// Create inset manager
|
||||
final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
|
||||
mRootView);
|
||||
|
||||
// Create session
|
||||
final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
|
||||
|
||||
// Add a view to the session.
|
||||
final Rect firstViewBounds = new Rect(0, 0, 2, 2);
|
||||
session.addViewToTracking(createView(firstViewBounds));
|
||||
|
||||
// only capture second invocation.
|
||||
mFakeExecutor.runAllReady();
|
||||
clearInvocations(mRootViewImpl);
|
||||
|
||||
// Add a second view to the session
|
||||
final Rect secondViewBounds = new Rect(4, 4, 9, 10);
|
||||
final View secondView = createView(secondViewBounds);
|
||||
session.addViewToTracking(secondView);
|
||||
|
||||
mFakeExecutor.runAllReady();
|
||||
|
||||
// Check to see if all views and sessions was properly accounted for.
|
||||
{
|
||||
final Region expectedRegion = Region.obtain();
|
||||
expectedRegion.op(firstViewBounds, Region.Op.UNION);
|
||||
expectedRegion.op(secondViewBounds, Region.Op.UNION);
|
||||
verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
|
||||
}
|
||||
|
||||
// Remove second view.
|
||||
session.removeViewFromTracking(secondView);
|
||||
|
||||
clearInvocations(mRootViewImpl);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
private View createView(Rect bounds) {
|
||||
final Rect rect = new Rect(bounds);
|
||||
final View view = Mockito.mock(View.class);
|
||||
doAnswer(invocation -> {
|
||||
((Rect) invocation.getArgument(0)).set(rect);
|
||||
return null;
|
||||
}).when(view).getBoundsOnScreen(any());
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user