Merge "Add media entry Dream complication." into tm-qpr-dev

This commit is contained in:
Xiaowen Lei
2022-08-31 16:24:30 +00:00
committed by Android (Google) Code Review
10 changed files with 378 additions and 13 deletions

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_entry_chip"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
android:layout_gravity="bottom|start"
android:scaleType="center"
android:tint="?android:attr/textColorPrimary"
android:src="@drawable/ic_music_note"
android:background="@drawable/keyguard_bottom_affordance_bg"
android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:contentDescription="@string/controls_media_title" />

View File

@@ -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) {

View File

@@ -151,8 +151,8 @@ public class DreamHomeControlsComplication implements Complication {
* Controls behavior of the dream complication.
*/
static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
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;

View File

@@ -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<View> {
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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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.
*/

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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<View.OnClickListener> 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);
}
}

View File

@@ -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();