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:
Bryce Lee
2021-10-25 09:45:59 -07:00
parent 54b8f33ec7
commit f0d376d45d
5 changed files with 331 additions and 2 deletions

View File

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

View File

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

View File

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

View File

@@ -109,7 +109,6 @@ public class AppWidgetOverlayProviderTest extends SysuiTestCase {
mInteractionHandler = creationCallbackCapture.getValue();
}
@Test
public void testWidgetBringup() {
// Make sure widget was requested.

View File

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