From ea8cd10083ac4d257b4f9b002b50a3789e868848 Mon Sep 17 00:00:00 2001 From: Xiaowen Lei Date: Tue, 23 Aug 2022 00:22:57 +0000 Subject: Add media entry Dream complication. This implements using the entry chip to show/hide UMO, not yet the timeout behavior. Also, the chip visual isn't updated when it's clicked. The layout weight for the UMO complication is updated to ensure it's placed below the Smartspace, not above it. Also fixed a javadoc broken link in ComplicationUtils.java. Bug: 242613318 Test: manual Test: atest MediaDreamSentinelTest Change-Id: Ic1eb7585007493be509ccce50c68e2d5a91583fe --- .../res/layout/dream_overlay_media_entry_chip.xml | 29 +++++ .../dreams/complication/ComplicationUtils.java | 2 +- .../DreamHomeControlsComplication.java | 2 +- .../complication/DreamMediaEntryComplication.java | 135 +++++++++++++++++++++ .../DreamMediaEntryComplicationComponent.java | 81 +++++++++++++ .../dagger/RegisteredComplicationsModule.java | 19 +++ .../systemui/media/dream/MediaDreamSentinel.java | 11 +- .../dream/dagger/MediaComplicationComponent.java | 4 +- .../DreamMediaEntryComplicationTest.java | 94 ++++++++++++++ .../media/dream/MediaDreamSentinelTest.java | 14 ++- 10 files changed, 378 insertions(+), 13 deletions(-) create mode 100644 packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamMediaEntryComplicationComponent.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java diff --git a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml new file mode 100644 index 000000000000..50f3ffcaf968 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml @@ -0,0 +1,29 @@ + + + diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java index d5db63dc9093..75a97de10e7e 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java @@ -35,7 +35,7 @@ import java.util.Set; public class ComplicationUtils { /** * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to - * {@link ComplicationType}. + * {@link Complication.ComplicationType}. */ @Complication.ComplicationType public static int convertComplicationType(@DreamBackend.ComplicationType int type) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index 1c72e49eb06a..2503d3c3edad 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -151,8 +151,8 @@ public class DreamHomeControlsComplication implements Complication { * Controls behavior of the dream complication. */ static class DreamHomeControlsChipViewController extends ViewController { - private static final boolean DEBUG = false; private static final String TAG = "DreamHomeControlsCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ActivityStarter mActivityStarter; private final Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java new file mode 100644 index 000000000000..21a51d1096d5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java @@ -0,0 +1,135 @@ +/* + * 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.complication; + +import static com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent.DreamMediaEntryModule.DREAM_MEDIA_ENTRY_VIEW; +import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_ENTRY_LAYOUT_PARAMS; + +import android.util.Log; +import android.view.View; + +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent; +import com.android.systemui.media.dream.MediaDreamComplication; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * A dream complication that shows a media entry chip to launch media control view. + */ +public class DreamMediaEntryComplication implements Complication { + private final DreamMediaEntryComplicationComponent.Factory mComponentFactory; + + @Inject + public DreamMediaEntryComplication( + DreamMediaEntryComplicationComponent.Factory componentFactory) { + mComponentFactory = componentFactory; + } + + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create().getViewHolder(); + } + + /** + * Contains values/logic associated with the dream complication view. + */ + public static class DreamMediaEntryViewHolder implements ViewHolder { + private final View mView; + private final ComplicationLayoutParams mLayoutParams; + private final DreamMediaEntryViewController mViewController; + + @Inject + DreamMediaEntryViewHolder( + DreamMediaEntryViewController dreamMediaEntryViewController, + @Named(DREAM_MEDIA_ENTRY_VIEW) View view, + @Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams + ) { + mView = view; + mLayoutParams = layoutParams; + mViewController = dreamMediaEntryViewController; + mViewController.init(); + } + + @Override + public View getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } + + /** + * Controls behavior of the dream complication. + */ + static class DreamMediaEntryViewController extends ViewController { + private static final String TAG = "DreamMediaEntryVwCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final DreamOverlayStateController mDreamOverlayStateController; + private final MediaDreamComplication mMediaComplication; + + private boolean mMediaComplicationAdded; + + @Inject + DreamMediaEntryViewController( + @Named(DREAM_MEDIA_ENTRY_VIEW) View view, + DreamOverlayStateController dreamOverlayStateController, + MediaDreamComplication mediaComplication) { + super(view); + mDreamOverlayStateController = dreamOverlayStateController; + mMediaComplication = mediaComplication; + mView.setOnClickListener(this::onClickMediaEntry); + } + + @Override + protected void onViewAttached() { + } + + @Override + protected void onViewDetached() { + removeMediaComplication(); + } + + private void onClickMediaEntry(View v) { + if (DEBUG) Log.d(TAG, "media entry complication tapped"); + + if (!mMediaComplicationAdded) { + addMediaComplication(); + } else { + removeMediaComplication(); + } + } + + private void addMediaComplication() { + mView.setSelected(true); + mDreamOverlayStateController.addComplication(mMediaComplication); + mMediaComplicationAdded = true; + } + + private void removeMediaComplication() { + mView.setSelected(false); + mDreamOverlayStateController.removeComplication(mMediaComplication); + mMediaComplicationAdded = false; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamMediaEntryComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamMediaEntryComplicationComponent.java new file mode 100644 index 000000000000..ed05daf35ed9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamMediaEntryComplicationComponent.java @@ -0,0 +1,81 @@ +/* + * 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.complication.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.view.LayoutInflater; +import android.view.View; + +import com.android.systemui.R; +import com.android.systemui.dreams.complication.DreamMediaEntryComplication; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * Responsible for generating dependencies for the {@link DreamMediaEntryComplication}. + */ +@Subcomponent(modules = DreamMediaEntryComplicationComponent.DreamMediaEntryModule.class) +@DreamMediaEntryComplicationComponent.DreamMediaEntryComplicationScope +public interface DreamMediaEntryComplicationComponent { + /** + * Creates a view holder for the media entry complication. + */ + DreamMediaEntryComplication.DreamMediaEntryViewHolder getViewHolder(); + + /** + * Scope of the media entry complication. + */ + @Documented + @Retention(RUNTIME) + @Scope + @interface DreamMediaEntryComplicationScope {} + + /** + * Factory that generates a {@link DreamMediaEntryComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + DreamMediaEntryComplicationComponent create(); + } + + /** + * Scoped injected values for the {@link DreamMediaEntryComplicationComponent}. + */ + @Module + interface DreamMediaEntryModule { + String DREAM_MEDIA_ENTRY_VIEW = "dream_media_entry_view"; + + /** + * Provides the dream media entry view. + */ + @Provides + @DreamMediaEntryComplicationScope + @Named(DREAM_MEDIA_ENTRY_VIEW) + static View provideMediaEntryView(LayoutInflater layoutInflater) { + return (View) layoutInflater.inflate(R.layout.dream_overlay_media_entry_chip, null); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java index eb07238ce752..759d6ec3bf21 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -38,15 +38,19 @@ import dagger.Provides; }, subcomponents = { DreamHomeControlsComplicationComponent.class, + DreamMediaEntryComplicationComponent.class }) public interface RegisteredComplicationsModule { String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS = "time_complication_layout_params"; String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params"; String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params"; + String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0; + int DREAM_MEDIA_COMPLICATION_WEIGHT = -1; int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1; + int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 0; /** * Provides layout parameters for the clock time complication. @@ -77,6 +81,21 @@ public interface RegisteredComplicationsModule { DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT); } + /** + * Provides layout parameters for the media entry complication. + */ + @Provides + @Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS) + static ComplicationLayoutParams provideMediaEntryLayoutParams(@Main Resources res) { + return new ComplicationLayoutParams( + res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), + res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_END, + DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT); + } + /** * Provides layout parameters for the smartspace complication. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index c5448713970c..acd04f216a98 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -25,6 +25,7 @@ import androidx.annotation.Nullable; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.DreamMediaEntryComplication; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; @@ -54,7 +55,7 @@ public class MediaDreamSentinel extends CoreStartable { } mAdded = false; - mDreamOverlayStateController.removeComplication(mComplication); + mDreamOverlayStateController.removeComplication(mMediaEntryComplication); } @Override @@ -79,24 +80,24 @@ public class MediaDreamSentinel extends CoreStartable { } mAdded = true; - mDreamOverlayStateController.addComplication(mComplication); + mDreamOverlayStateController.addComplication(mMediaEntryComplication); } }; private final MediaDataManager mMediaDataManager; private final DreamOverlayStateController mDreamOverlayStateController; - private final MediaDreamComplication mComplication; + private final DreamMediaEntryComplication mMediaEntryComplication; private final FeatureFlags mFeatureFlags; @Inject public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager, DreamOverlayStateController dreamOverlayStateController, - MediaDreamComplication complication, + DreamMediaEntryComplication mediaEntryComplication, FeatureFlags featureFlags) { super(context); mMediaDataManager = mediaDataManager; mDreamOverlayStateController = dreamOverlayStateController; - mComplication = complication; + mMediaEntryComplication = mediaEntryComplication; mFeatureFlags = featureFlags; } diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java index 3408d97dd33f..052608f17e0e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java @@ -16,6 +16,8 @@ package com.android.systemui.media.dream.dagger; +import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_MEDIA_COMPLICATION_WEIGHT; + import static java.lang.annotation.RetentionPolicy.RUNTIME; import android.content.Context; @@ -93,7 +95,7 @@ public interface MediaComplicationComponent { ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START, ComplicationLayoutParams.DIRECTION_DOWN, - 0, + DREAM_MEDIA_COMPLICATION_WEIGHT, true); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java new file mode 100644 index 000000000000..bc944404efe6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java @@ -0,0 +1,94 @@ +/* + * 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.complication; + +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.media.dream.MediaDreamComplication; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DreamMediaEntryComplicationTest extends SysuiTestCase { + @Mock + private View mView; + + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + + @Mock + private MediaDreamComplication mMediaComplication; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensures clicking media entry chip adds/removes media complication. + */ + @Test + public void testClick() { + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication); + + final ArgumentCaptor clickListenerCaptor = + ArgumentCaptor.forClass(View.OnClickListener.class); + verify(mView).setOnClickListener(clickListenerCaptor.capture()); + + clickListenerCaptor.getValue().onClick(mView); + verify(mView).setSelected(true); + verify(mDreamOverlayStateController).addComplication(mMediaComplication); + clickListenerCaptor.getValue().onClick(mView); + verify(mView).setSelected(false); + verify(mDreamOverlayStateController).removeComplication(mMediaComplication); + } + + /** + * Ensures media complication is removed when the view is detached. + */ + @Test + public void testOnViewDetached() { + final DreamMediaEntryComplication.DreamMediaEntryViewController viewController = + new DreamMediaEntryComplication.DreamMediaEntryViewController( + mView, + mDreamOverlayStateController, + mMediaComplication); + + viewController.onViewDetached(); + verify(mView).setSelected(false); + verify(mDreamOverlayStateController).removeComplication(mMediaComplication); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index c101b9ffd495..2e864dc2cda8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -18,6 +18,7 @@ package com.android.systemui.media.dream; import static com.android.systemui.flags.Flags.MEDIA_DREAM_COMPLICATION; +import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -30,6 +31,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.DreamMediaEntryComplication; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.MediaData; import com.android.systemui.media.MediaDataManager; @@ -51,7 +53,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { DreamOverlayStateController mDreamOverlayStateController; @Mock - MediaDreamComplication mComplication; + DreamMediaEntryComplication mMediaEntryComplication; @Mock FeatureFlags mFeatureFlags; @@ -72,7 +74,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { @Test public void testComplicationAddition() { final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, - mDreamOverlayStateController, mComplication, mFeatureFlags); + mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); @@ -85,14 +87,16 @@ public class MediaDreamSentinelTest extends SysuiTestCase { when(mMediaDataManager.hasActiveMedia()).thenReturn(true); listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true, /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false); - verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + verify(mDreamOverlayStateController).addComplication(eq(mMediaEntryComplication)); + verify(mDreamOverlayStateController, never()).addComplication( + not(eq(mMediaEntryComplication))); listener.onMediaDataRemoved(mKey); verify(mDreamOverlayStateController, never()).removeComplication(any()); when(mMediaDataManager.hasActiveMedia()).thenReturn(false); listener.onMediaDataRemoved(mKey); - verify(mDreamOverlayStateController).removeComplication(eq(mComplication)); + verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication)); } @Test @@ -100,7 +104,7 @@ public class MediaDreamSentinelTest extends SysuiTestCase { when(mFeatureFlags.isEnabled(MEDIA_DREAM_COMPLICATION)).thenReturn(false); final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, - mDreamOverlayStateController, mComplication, mFeatureFlags); + mDreamOverlayStateController, mMediaEntryComplication, mFeatureFlags); sentinel.start(); -- cgit v1.2.3-59-g8ed1b