Allow App Widget Overlays by Product Configuration.
This changelist adds support for loading a set of app widget overlays to be shown over dreams from config. Products can specify the components to show along with the position within the parent by gravity. Bug: 201676043 Test: atest AppWidgetOverlayPrimerTest Change-Id: Ic2f706598f776e0802106e333c4003242227faea
This commit is contained in:
@@ -715,7 +715,22 @@
|
||||
<!-- Flag to enable privacy dot views, it shall be true for normal case -->
|
||||
<bool name="config_enablePrivacyDot">true</bool>
|
||||
|
||||
<!-- The positions widgets can be in defined as View.Gravity constants -->
|
||||
<integer-array name="config_dreamOverlayPositions">
|
||||
</integer-array>
|
||||
|
||||
<!-- Widget components to show as dream overlays -->
|
||||
<string-array name="config_dreamOverlayComponents" translatable="false">
|
||||
</string-array>
|
||||
|
||||
<!-- Width percentage of dream overlay components -->
|
||||
<item name="config_dreamOverlayComponentWidthPercent" translatable="false" format="float"
|
||||
type="dimen">0.33</item>
|
||||
|
||||
<!-- Height percentage of dream overlay components -->
|
||||
<item name="config_dreamOverlayComponentHeightPercent" translatable="false" format="float"
|
||||
type="dimen">0.25</item>
|
||||
|
||||
<!-- Flag to enable dream overlay service and its registration -->
|
||||
<bool name="config_dreamOverlayServiceEnabled">false</bool>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.android.systemui.accessibility.SystemActions;
|
||||
import com.android.systemui.accessibility.WindowMagnification;
|
||||
import com.android.systemui.biometrics.AuthController;
|
||||
import com.android.systemui.dreams.DreamOverlayRegistrant;
|
||||
import com.android.systemui.dreams.appwidgets.AppWidgetOverlayPrimer;
|
||||
import com.android.systemui.globalactions.GlobalActionsComponent;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
import com.android.systemui.keyguard.dagger.KeyguardModule;
|
||||
@@ -196,4 +197,11 @@ public abstract class SystemUIBinder {
|
||||
@ClassKey(DreamOverlayRegistrant.class)
|
||||
public abstract SystemUI bindDreamOverlayRegistrant(
|
||||
DreamOverlayRegistrant dreamOverlayRegistrant);
|
||||
|
||||
/** Inject into AppWidgetOverlayPrimer. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(AppWidgetOverlayPrimer.class)
|
||||
public abstract SystemUI bindAppWidgetOverlayPrimer(
|
||||
AppWidgetOverlayPrimer appWidgetOverlayPrimer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.appwidgets;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.view.Gravity;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintSet;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SystemUI;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dreams.DreamOverlayStateController;
|
||||
import com.android.systemui.dreams.OverlayHostView;
|
||||
import com.android.systemui.dreams.dagger.AppWidgetOverlayComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link AppWidgetOverlayPrimer} reads the configured App Widget Overlay from resources on start
|
||||
* and populates them into the {@link DreamOverlayStateController}.
|
||||
*/
|
||||
public class AppWidgetOverlayPrimer extends SystemUI {
|
||||
private final Resources mResources;
|
||||
private final DreamOverlayStateController mDreamOverlayStateController;
|
||||
private final AppWidgetOverlayComponent.Factory mComponentFactory;
|
||||
|
||||
@Inject
|
||||
public AppWidgetOverlayPrimer(Context context, @Main Resources resources,
|
||||
DreamOverlayStateController overlayStateController,
|
||||
AppWidgetOverlayComponent.Factory appWidgetOverlayFactory) {
|
||||
super(context);
|
||||
mResources = resources;
|
||||
mDreamOverlayStateController = overlayStateController;
|
||||
mComponentFactory = appWidgetOverlayFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBootCompleted() {
|
||||
super.onBootCompleted();
|
||||
loadDefaultWidgets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the {@link OverlayHostView.LayoutParams} for a given gravity. Default dimension
|
||||
* constraints are also included in the params.
|
||||
* @param gravity The gravity for the layout as defined by {@link Gravity}.
|
||||
* @param resources The resourcs from which default dimensions will be extracted from.
|
||||
* @return {@link OverlayHostView.LayoutParams} representing the provided gravity and default
|
||||
* parameters.
|
||||
*/
|
||||
private static OverlayHostView.LayoutParams getLayoutParams(int gravity, Resources resources) {
|
||||
final OverlayHostView.LayoutParams params = new OverlayHostView.LayoutParams(
|
||||
OverlayHostView.LayoutParams.MATCH_CONSTRAINT,
|
||||
OverlayHostView.LayoutParams.MATCH_CONSTRAINT);
|
||||
|
||||
if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
|
||||
params.bottomToBottom = ConstraintSet.PARENT_ID;
|
||||
}
|
||||
|
||||
if ((gravity & Gravity.TOP) == Gravity.TOP) {
|
||||
params.topToTop = ConstraintSet.PARENT_ID;
|
||||
}
|
||||
|
||||
if ((gravity & Gravity.END) == Gravity.END) {
|
||||
params.endToEnd = ConstraintSet.PARENT_ID;
|
||||
}
|
||||
|
||||
if ((gravity & Gravity.START) == Gravity.START) {
|
||||
params.startToStart = ConstraintSet.PARENT_ID;
|
||||
}
|
||||
|
||||
// For now, apply the same sizing constraints on every widget.
|
||||
params.matchConstraintPercentHeight =
|
||||
resources.getFloat(R.dimen.config_dreamOverlayComponentHeightPercent);
|
||||
params.matchConstraintPercentWidth =
|
||||
resources.getFloat(R.dimen.config_dreamOverlayComponentWidthPercent);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method for loading widgets based on configuration.
|
||||
*/
|
||||
private void loadDefaultWidgets() {
|
||||
final int[] positions = mResources.getIntArray(R.array.config_dreamOverlayPositions);
|
||||
final String[] components =
|
||||
mResources.getStringArray(R.array.config_dreamOverlayComponents);
|
||||
|
||||
for (int i = 0; i < Math.min(positions.length, components.length); i++) {
|
||||
final AppWidgetOverlayComponent component = mComponentFactory.build(
|
||||
ComponentName.unflattenFromString(components[i]),
|
||||
getLayoutParams(positions[i], mResources));
|
||||
|
||||
mDreamOverlayStateController.addOverlay(component.getAppWidgetOverlayProvider());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,6 @@ public class AppWidgetOverlayProviderTest extends SysuiTestCase {
|
||||
mInteractionHandler = creationCallbackCapture.getValue();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWidgetBringup() {
|
||||
// Make sure widget was requested.
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.appwidgets;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.res.Resources;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.view.Gravity;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.SysuiTestableContext;
|
||||
import com.android.systemui.dreams.DreamOverlayStateController;
|
||||
import com.android.systemui.dreams.dagger.AppWidgetOverlayComponent;
|
||||
import com.android.systemui.utils.leaks.LeakCheckedTest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
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)
|
||||
public class AppWidgetOverlayPrimerTest extends SysuiTestCase {
|
||||
@Rule
|
||||
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
|
||||
|
||||
@Rule
|
||||
public SysuiTestableContext mContext = new SysuiTestableContext(
|
||||
InstrumentationRegistry.getContext(), mLeakCheck);
|
||||
|
||||
@Mock
|
||||
Resources mResources;
|
||||
|
||||
@Mock
|
||||
AppWidgetOverlayComponent mAppWidgetOverlayComponent1;
|
||||
@Mock
|
||||
AppWidgetOverlayComponent mAppWidgetOverlayComponent2;
|
||||
|
||||
@Mock
|
||||
AppWidgetOverlayProvider mAppWidgetOverlayProvider1;
|
||||
|
||||
@Mock
|
||||
AppWidgetOverlayProvider mAppWidgetOverlayProvider2;
|
||||
|
||||
final ComponentName mAppOverlayComponent1 =
|
||||
ComponentName.unflattenFromString("com.foo.bar/.Baz");
|
||||
final ComponentName mAppOverlayComponent2 =
|
||||
ComponentName.unflattenFromString("com.foo.bar/.Baz2");
|
||||
|
||||
final int mAppOverlayGravity1 = Gravity.BOTTOM | Gravity.START;
|
||||
final int mAppOverlayGravity2 = Gravity.BOTTOM | Gravity.END;
|
||||
|
||||
final String[] mComponents = new String[]{mAppOverlayComponent1.flattenToString(),
|
||||
mAppOverlayComponent2.flattenToString() };
|
||||
final int[] mPositions = new int[]{ mAppOverlayGravity1, mAppOverlayGravity2 };
|
||||
|
||||
@Mock
|
||||
DreamOverlayStateController mDreamOverlayStateController;
|
||||
|
||||
@Mock
|
||||
AppWidgetOverlayComponent.Factory mAppWidgetOverlayProviderFactory;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mAppWidgetOverlayProviderFactory.build(eq(mAppOverlayComponent1), any()))
|
||||
.thenReturn(mAppWidgetOverlayComponent1);
|
||||
when(mAppWidgetOverlayComponent1.getAppWidgetOverlayProvider())
|
||||
.thenReturn(mAppWidgetOverlayProvider1);
|
||||
when(mAppWidgetOverlayProviderFactory.build(eq(mAppOverlayComponent2), any()))
|
||||
.thenReturn(mAppWidgetOverlayComponent2);
|
||||
when(mAppWidgetOverlayComponent2.getAppWidgetOverlayProvider())
|
||||
.thenReturn(mAppWidgetOverlayProvider2);
|
||||
when(mResources.getIntArray(R.array.config_dreamOverlayPositions)).thenReturn(mPositions);
|
||||
when(mResources.getStringArray(R.array.config_dreamOverlayComponents))
|
||||
.thenReturn(mComponents);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoading() {
|
||||
final AppWidgetOverlayPrimer primer = new AppWidgetOverlayPrimer(mContext,
|
||||
mResources,
|
||||
mDreamOverlayStateController,
|
||||
mAppWidgetOverlayProviderFactory);
|
||||
|
||||
// Inform primer to begin.
|
||||
primer.onBootCompleted();
|
||||
|
||||
// Verify the first component is added to the state controller with the proper position.
|
||||
{
|
||||
final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
|
||||
verify(mAppWidgetOverlayProviderFactory, times(1)).build(eq(mAppOverlayComponent1),
|
||||
layoutParamsArgumentCaptor.capture());
|
||||
|
||||
assertEquals(layoutParamsArgumentCaptor.getValue().startToStart,
|
||||
ConstraintLayout.LayoutParams.PARENT_ID);
|
||||
assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
|
||||
ConstraintLayout.LayoutParams.PARENT_ID);
|
||||
|
||||
verify(mDreamOverlayStateController, times(1))
|
||||
.addOverlay(eq(mAppWidgetOverlayProvider1));
|
||||
}
|
||||
|
||||
// Verify the second component is added to the state controller with the proper position.
|
||||
{
|
||||
final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
|
||||
verify(mAppWidgetOverlayProviderFactory, times(1)).build(eq(mAppOverlayComponent2),
|
||||
layoutParamsArgumentCaptor.capture());
|
||||
|
||||
assertEquals(layoutParamsArgumentCaptor.getValue().endToEnd,
|
||||
ConstraintLayout.LayoutParams.PARENT_ID);
|
||||
assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
|
||||
ConstraintLayout.LayoutParams.PARENT_ID);
|
||||
verify(mDreamOverlayStateController, times(1))
|
||||
.addOverlay(eq(mAppWidgetOverlayProvider1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoComponents() {
|
||||
when(mResources.getStringArray(R.array.config_dreamOverlayComponents))
|
||||
.thenReturn(new String[]{});
|
||||
|
||||
final AppWidgetOverlayPrimer primer = new AppWidgetOverlayPrimer(mContext,
|
||||
mResources,
|
||||
mDreamOverlayStateController,
|
||||
mAppWidgetOverlayProviderFactory);
|
||||
|
||||
// Inform primer to begin.
|
||||
primer.onBootCompleted();
|
||||
|
||||
|
||||
// Make sure there is no request to add a widget if no components are specified by the
|
||||
// product.
|
||||
verify(mAppWidgetOverlayProviderFactory, never()).build(any(), any());
|
||||
verify(mDreamOverlayStateController, never()).addOverlay(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoPositions() {
|
||||
when(mResources.getIntArray(R.array.config_dreamOverlayPositions))
|
||||
.thenReturn(new int[]{});
|
||||
|
||||
final AppWidgetOverlayPrimer primer = new AppWidgetOverlayPrimer(mContext,
|
||||
mResources,
|
||||
mDreamOverlayStateController,
|
||||
mAppWidgetOverlayProviderFactory);
|
||||
|
||||
primer.onBootCompleted();
|
||||
|
||||
// Make sure there is no request to add a widget if no positions are specified by the
|
||||
// product.
|
||||
verify(mAppWidgetOverlayProviderFactory, never()).build(any(), any());
|
||||
verify(mDreamOverlayStateController, never()).addOverlay(any());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user