Remove SmartSpaceComplication when the SmartspaceTargetListener is removed.

The listener removes the SmartSpaceComplication when the targets becomes
empty. If the listener isn't connected, we don't know whether/when the
targets becomes empty. Therefore we should just remove the complication
when the listener is removed.

This fixes the extra DATE card impression on the Dream surface. The
CardPagerAdapter adds a default DATE card when its setTargets method is
called with an empty list. This CL ensures that for Dream Smartspace,
the setTargets method is never called with an empty list.

Reorganized the existing testAvailability() test into multiple tests.
The new logic is tested in
testOverlayInActive_removesTargetListener_removesComplication().

Bug: 231251252
Test: on device via `adb logcat ...`
Test: atest SmartSpaceComplicationTest
Change-Id: Iac0d8a9697f42e2c70c83c5da050f17190fcc3c1
This commit is contained in:
Xiaowen Lei
2022-06-21 22:55:10 +00:00
parent ce97eb6480
commit 2a2a8e1b2c
6 changed files with 107 additions and 9 deletions

View File

@@ -101,6 +101,9 @@ public class DreamOverlayStateController implements
public void addComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
if (DEBUG) {
Log.d(TAG, "addComplication: added " + complication);
}
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -112,6 +115,9 @@ public class DreamOverlayStateController implements
public void removeComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
if (DEBUG) {
Log.d(TAG, "removeComplication: removed " + complication);
}
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});

View File

@@ -82,6 +82,7 @@ public class SmartSpaceComplication implements Complication {
mSmartSpaceController.addListener(mSmartspaceListener);
} else {
mSmartSpaceController.removeListener(mSmartspaceListener);
mDreamOverlayStateController.removeComplication(mComplication);
}
}
});

View File

@@ -21,6 +21,7 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
import android.util.Log;
import android.view.View;
@@ -44,7 +45,8 @@ import javax.inject.Named;
* a {@link ComplicationLayoutEngine}.
*/
public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
public static final String TAG = "ComplicationHostVwCtrl";
private static final String TAG = "ComplicationHostVwCtrl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ComplicationLayoutEngine mLayoutEngine;
private final LifecycleOwner mLifecycleOwner;
@@ -90,6 +92,11 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
}
private void updateComplications(Collection<ComplicationViewModel> complications) {
if (DEBUG) {
Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25));
Log.d(TAG, " mComplications = " + mComplications.toString());
Log.d(TAG, " complications = " + complications.toString());
}
final Collection<ComplicationId> ids = complications.stream()
.map(complicationViewModel -> complicationViewModel.getId())
.collect(Collectors.toSet());

View File

@@ -54,7 +54,7 @@ import javax.inject.Named;
*/
@DreamOverlayComponent.DreamOverlayScope
public class ComplicationLayoutEngine implements Complication.VisibilityController {
public static final String TAG = "ComplicationLayoutEngine";
public static final String TAG = "ComplicationLayoutEng";
/**
* {@link ViewEntry} is an internal container, capturing information necessary for working with
@@ -529,7 +529,7 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll
*/
public void addComplication(ComplicationId id, View view,
ComplicationLayoutParams lp, @Complication.Category int category) {
Log.d(TAG, "engine: " + this + " addComplication");
Log.d(TAG, "@" + Integer.toHexString(this.hashCode()) + " addComplication: " + id);
// If the complication is present, remove.
if (mEntries.containsKey(id)) {

View File

@@ -64,4 +64,9 @@ public class ComplicationViewModel extends ViewModel {
public void exitDream() {
mHost.requestExitDream();
}
@Override
public String toString() {
return mId + "=" + mComplication.toString();
}
}

View File

@@ -72,19 +72,67 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
}
/**
* Ensures {@link SmartSpaceComplication} is only registered when it is available.
* Ensures {@link SmartSpaceComplication} isn't registered right away on start.
*/
@Test
public void testAvailability() {
public void testRegistrantStart_doesNotAddComplication() {
final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
}
final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
private SmartSpaceComplication.Registrant getRegistrant() {
return new SmartSpaceComplication.Registrant(
mContext,
mDreamOverlayStateController,
mComplication,
mSmartspaceController);
registrant.start();
verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
}
@Test
public void testOverlayActive_addsTargetListener() {
final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
dreamCallbackCaptor.getValue().onStateChanged();
// Test
final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
verify(mSmartspaceController).addListener(listenerCaptor.capture());
}
@Test
public void testOverlayActive_targetsNonEmpty_addsComplication() {
final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
dreamCallbackCaptor.getValue().onStateChanged();
final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
verify(mSmartspaceController).addListener(listenerCaptor.capture());
// Test
final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
}
@Test
public void testOverlayActive_targetsEmpty_removesComplication() {
final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
@@ -100,10 +148,41 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
// Test
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList());
verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
}
@Test
public void testGetViewReusesSameView() {
public void testOverlayInActive_removesTargetListener_removesComplication() {
final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
dreamCallbackCaptor.getValue().onStateChanged();
final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
verify(mSmartspaceController).addListener(listenerCaptor.capture());
final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
// Test
when(mDreamOverlayStateController.isOverlayActive()).thenReturn(false);
dreamCallbackCaptor.getValue().onStateChanged();
verify(mSmartspaceController).removeListener(listenerCaptor.getValue());
verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
}
@Test
public void testGetView_reusesSameView() {
final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
mSmartspaceController);
final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);