Merge "Remove AppWidget complication support."

This commit is contained in:
Bryce Lee
2022-01-21 06:49:52 +00:00
committed by Android (Google) Code Review
9 changed files with 1 additions and 699 deletions

View File

@@ -716,22 +716,6 @@
<!-- 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_dreamComplicationPositions">
</integer-array>
<!-- Widget components to show as dream complications -->
<string-array name="config_dreamAppWidgetComplications" translatable="false">
</string-array>
<!-- Width percentage of dream complications -->
<item name="config_dreamComplicationWidthPercent" translatable="false" format="float"
type="dimen">0.33</item>
<!-- Height percentage of dream complications -->
<item name="config_dreamComplicationHeightPercent" translatable="false" format="float"
type="dimen">0.25</item>
<!-- Flag to enable dream overlay service and its registration -->
<bool name="config_dreamOverlayServiceEnabled">false</bool>

View File

@@ -26,7 +26,6 @@ import com.android.systemui.accessibility.WindowMagnification;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.clipboardoverlay.ClipboardListener;
import com.android.systemui.dreams.DreamOverlayRegistrant;
import com.android.systemui.dreams.appwidgets.ComplicationPrimer;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -212,11 +211,4 @@ public abstract class SystemUIBinder {
@ClassKey(DreamOverlayRegistrant.class)
public abstract CoreStartable bindDreamOverlayRegistrant(
DreamOverlayRegistrant dreamOverlayRegistrant);
/** Inject into AppWidgetOverlayPrimer. */
@Binds
@IntoMap
@ClassKey(ComplicationPrimer.class)
public abstract CoreStartable bindAppWidgetOverlayPrimer(
ComplicationPrimer complicationPrimer);
}

View File

@@ -1,101 +0,0 @@
/*
* 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.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import java.util.List;
import javax.inject.Inject;
/**
* {@link AppWidgetProvider} is a singleton for accessing app widgets within SystemUI. This
* consolidates resources such as the {@link AppWidgetHost} across potentially multiple
* {@link ComplicationProvider} instances and other usages.
*/
@SysUISingleton
public class AppWidgetProvider {
private static final String TAG = "AppWidgetProvider";
public static final int APP_WIDGET_HOST_ID = 1025;
private final Context mContext;
private final AppWidgetManager mAppWidgetManager;
private final AppWidgetHost mAppWidgetHost;
private final Resources mResources;
@Inject
public AppWidgetProvider(Context context, @Main Resources resources) {
mContext = context;
mResources = resources;
mAppWidgetManager = android.appwidget.AppWidgetManager.getInstance(context);
mAppWidgetHost = new AppWidgetHost(context, APP_WIDGET_HOST_ID);
mAppWidgetHost.startListening();
}
/**
* Returns an {@link AppWidgetHostView} associated with a given {@link ComponentName}.
* @param component The {@link ComponentName} of the target {@link AppWidgetHostView}.
* @return The {@link AppWidgetHostView} or {@code null} on error.
*/
public AppWidgetHostView getWidget(ComponentName component) {
final List<AppWidgetProviderInfo> appWidgetInfos =
mAppWidgetManager.getInstalledProviders();
for (AppWidgetProviderInfo widgetInfo : appWidgetInfos) {
if (widgetInfo.provider.equals(component)) {
final int widgetId = mAppWidgetHost.allocateAppWidgetId();
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(widgetId,
widgetInfo.provider);
if (!success) {
Log.e(TAG, "could not bind to app widget:" + component);
break;
}
final AppWidgetHostView appWidgetView =
mAppWidgetHost.createView(mContext, widgetId, widgetInfo);
if (appWidgetView != null) {
// Register a layout change listener to update the widget on any sizing changes.
appWidgetView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
final float density = mResources.getDisplayMetrics().density;
final int height = Math.round((bottom - top) / density);
final int width = Math.round((right - left) / density);
appWidgetView.updateAppWidgetSize(null, width, height,
width, height);
});
}
return appWidgetView;
}
}
return null;
}
}

View File

@@ -1,120 +0,0 @@
/*
* 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.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.ComplicationHostView;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent;
import javax.inject.Inject;
/**
* {@link ComplicationPrimer} reads the configured AppWidget Complications from resources on start
* and populates them into the {@link DreamOverlayStateController}.
*/
public class ComplicationPrimer extends CoreStartable {
private final Resources mResources;
private final DreamOverlayStateController mDreamOverlayStateController;
private final AppWidgetComponent.Factory mComponentFactory;
@Inject
public ComplicationPrimer(Context context, @Main Resources resources,
DreamOverlayStateController overlayStateController,
AppWidgetComponent.Factory appWidgetOverlayFactory) {
super(context);
mResources = resources;
mDreamOverlayStateController = overlayStateController;
mComponentFactory = appWidgetOverlayFactory;
}
@Override
public void start() {
}
@Override
protected void onBootCompleted() {
super.onBootCompleted();
loadDefaultWidgets();
}
/**
* Generates the {@link ComplicationHostView.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 ComplicationHostView.LayoutParams} representing the provided gravity and
* default parameters.
*/
private static ComplicationHostView.LayoutParams getLayoutParams(int gravity,
Resources resources) {
final ComplicationHostView.LayoutParams params = new ComplicationHostView.LayoutParams(
ComplicationHostView.LayoutParams.MATCH_CONSTRAINT,
ComplicationHostView.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_dreamComplicationHeightPercent);
params.matchConstraintPercentWidth =
resources.getFloat(R.dimen.config_dreamComplicationWidthPercent);
return params;
}
/**
* Helper method for loading widgets based on configuration.
*/
private void loadDefaultWidgets() {
final int[] positions = mResources.getIntArray(R.array.config_dreamComplicationPositions);
final String[] components =
mResources.getStringArray(R.array.config_dreamAppWidgetComplications);
for (int i = 0; i < Math.min(positions.length, components.length); i++) {
final AppWidgetComponent component = mComponentFactory.build(
ComponentName.unflattenFromString(components[i]),
getLayoutParams(positions[i], mResources));
mDreamOverlayStateController.addComplication(
component.getAppWidgetComplicationProvider());
}
}
}

View File

@@ -1,83 +0,0 @@
/*
* 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.appwidget.AppWidgetHostView;
import android.content.ComponentName;
import android.content.Context;
import android.util.Log;
import android.widget.RemoteViews;
import com.android.systemui.dreams.ComplicationHost;
import com.android.systemui.dreams.ComplicationHostView;
import com.android.systemui.plugins.ActivityStarter;
import javax.inject.Inject;
/**
* {@link ComplicationProvider} is an implementation of
* {@link com.android.systemui.dreams.ComplicationProvider} for providing app widget-based
* complications.
*/
public class ComplicationProvider implements com.android.systemui.dreams.ComplicationProvider {
private static final String TAG = "AppWidgetCompProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ActivityStarter mActivityStarter;
private final AppWidgetProvider mAppWidgetProvider;
private final ComponentName mComponentName;
private final ComplicationHostView.LayoutParams mLayoutParams;
@Inject
public ComplicationProvider(ActivityStarter activityStarter,
ComponentName componentName, AppWidgetProvider widgetProvider,
ComplicationHostView.LayoutParams layoutParams) {
mActivityStarter = activityStarter;
mComponentName = componentName;
mAppWidgetProvider = widgetProvider;
mLayoutParams = layoutParams;
}
@Override
public void onCreateComplication(Context context,
ComplicationHost.CreationCallback creationCallback,
ComplicationHost.InteractionCallback interactionCallback) {
final AppWidgetHostView widget = mAppWidgetProvider.getWidget(mComponentName);
if (widget == null) {
Log.e(TAG, "could not create widget");
return;
}
widget.setInteractionHandler((view, pendingIntent, response) -> {
if (pendingIntent.isActivity()) {
if (DEBUG) {
Log.d(TAG, "launching pending intent from app widget:" + mComponentName);
}
interactionCallback.onExit();
mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent,
null /*intentSentUiThreadCallback*/, view);
return true;
} else {
return RemoteViews.startPendingIntent(view, pendingIntent,
response.getLaunchOptions(view));
}
});
creationCallback.onCreated(widget, mLayoutParams);
}
}

View File

@@ -1,39 +0,0 @@
/*
* 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.dagger;
import android.content.ComponentName;
import com.android.systemui.dreams.ComplicationHostView;
import com.android.systemui.dreams.appwidgets.ComplicationProvider;
import dagger.BindsInstance;
import dagger.Subcomponent;
/** */
@Subcomponent
public interface AppWidgetComponent {
/** */
@Subcomponent.Factory
interface Factory {
AppWidgetComponent build(@BindsInstance ComponentName component,
@BindsInstance ComplicationHostView.LayoutParams layoutParams);
}
/** Builds a {@link ComplicationProvider}. */
ComplicationProvider getAppWidgetComplicationProvider();
}

View File

@@ -16,15 +16,12 @@
package com.android.systemui.dreams.dagger;
import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent;
import dagger.Module;
/**
* Dagger Module providing Communal-related functionality.
*/
@Module(subcomponents = {
AppWidgetComponent.class,
DreamOverlayComponent.class})
public interface DreamModule {
}
}

View File

@@ -1,191 +0,0 @@
/*
* 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.appwidgets.dagger.AppWidgetComponent;
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 ComplicationPrimerTest extends SysuiTestCase {
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
@Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
InstrumentationRegistry.getContext(), mLeakCheck);
@Mock
Resources mResources;
@Mock
AppWidgetComponent mAppWidgetComplicationComponent1;
@Mock
AppWidgetComponent mAppWidgetComplicationComponent2;
@Mock
ComplicationProvider mComplicationProvider1;
@Mock
ComplicationProvider mComplicationProvider2;
final ComponentName mAppComplicationComponent1 =
ComponentName.unflattenFromString("com.foo.bar/.Baz");
final ComponentName mAppComplicationComponent2 =
ComponentName.unflattenFromString("com.foo.bar/.Baz2");
final int mAppComplicationGravity1 = Gravity.BOTTOM | Gravity.START;
final int mAppComplicationGravity2 = Gravity.BOTTOM | Gravity.END;
final String[] mComponents = new String[]{mAppComplicationComponent1.flattenToString(),
mAppComplicationComponent2.flattenToString() };
final int[] mPositions = new int[]{mAppComplicationGravity1, mAppComplicationGravity2};
@Mock
DreamOverlayStateController mDreamOverlayStateController;
@Mock
AppWidgetComponent.Factory mAppWidgetComplicationProviderFactory;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent1), any()))
.thenReturn(mAppWidgetComplicationComponent1);
when(mAppWidgetComplicationComponent1.getAppWidgetComplicationProvider())
.thenReturn(mComplicationProvider1);
when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent2), any()))
.thenReturn(mAppWidgetComplicationComponent2);
when(mAppWidgetComplicationComponent2.getAppWidgetComplicationProvider())
.thenReturn(mComplicationProvider2);
when(mResources.getIntArray(R.array.config_dreamComplicationPositions))
.thenReturn(mPositions);
when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications))
.thenReturn(mComponents);
}
@Test
public void testLoading() {
final ComplicationPrimer primer = new ComplicationPrimer(mContext,
mResources,
mDreamOverlayStateController,
mAppWidgetComplicationProviderFactory);
// 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(mAppWidgetComplicationProviderFactory, times(1))
.build(eq(mAppComplicationComponent1),
layoutParamsArgumentCaptor.capture());
assertEquals(layoutParamsArgumentCaptor.getValue().startToStart,
ConstraintLayout.LayoutParams.PARENT_ID);
assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
ConstraintLayout.LayoutParams.PARENT_ID);
verify(mDreamOverlayStateController, times(1))
.addComplication(eq(mComplicationProvider1));
}
// 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(mAppWidgetComplicationProviderFactory, times(1))
.build(eq(mAppComplicationComponent2),
layoutParamsArgumentCaptor.capture());
assertEquals(layoutParamsArgumentCaptor.getValue().endToEnd,
ConstraintLayout.LayoutParams.PARENT_ID);
assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
ConstraintLayout.LayoutParams.PARENT_ID);
verify(mDreamOverlayStateController, times(1))
.addComplication(eq(mComplicationProvider1));
}
}
@Test
public void testNoComponents() {
when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications))
.thenReturn(new String[]{});
final ComplicationPrimer primer = new ComplicationPrimer(mContext,
mResources,
mDreamOverlayStateController,
mAppWidgetComplicationProviderFactory);
// 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(mAppWidgetComplicationProviderFactory, never()).build(any(), any());
verify(mDreamOverlayStateController, never()).addComplication(any());
}
@Test
public void testNoPositions() {
when(mResources.getIntArray(R.array.config_dreamComplicationPositions))
.thenReturn(new int[]{});
final ComplicationPrimer primer = new ComplicationPrimer(mContext,
mResources,
mDreamOverlayStateController,
mAppWidgetComplicationProviderFactory);
primer.onBootCompleted();
// Make sure there is no request to add a widget if no positions are specified by the
// product.
verify(mAppWidgetComplicationProviderFactory, never()).build(any(), any());
verify(mDreamOverlayStateController, never()).addComplication(any());
}
}

View File

@@ -1,137 +0,0 @@
/*
* 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.appwidgets;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
import android.appwidget.AppWidgetHostView;
import android.content.ComponentName;
import android.testing.AndroidTestingRunner;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.dreams.ComplicationHost;
import com.android.systemui.dreams.ComplicationHostView;
import com.android.systemui.plugins.ActivityStarter;
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 ComplicationProviderTest extends SysuiTestCase {
@Mock
ActivityStarter mActivityStarter;
@Mock
ComponentName mComponentName;
@Mock
AppWidgetProvider mAppWidgetProvider;
@Mock
AppWidgetHostView mAppWidgetHostView;
@Mock
ComplicationHost.CreationCallback mCreationCallback;
@Mock
ComplicationHost.InteractionCallback mInteractionCallback;
@Mock
PendingIntent mPendingIntent;
@Mock
RemoteViews.RemoteResponse mRemoteResponse;
ComplicationProvider mComplicationProvider;
RemoteViews.InteractionHandler mInteractionHandler;
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
@Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
InstrumentationRegistry.getContext(), mLeakCheck);
ComplicationHostView.LayoutParams mLayoutParams = new ComplicationHostView.LayoutParams(
ComplicationHostView.LayoutParams.MATCH_PARENT,
ComplicationHostView.LayoutParams.MATCH_PARENT);
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mPendingIntent.isActivity()).thenReturn(true);
when(mAppWidgetProvider.getWidget(mComponentName)).thenReturn(mAppWidgetHostView);
mComplicationProvider = new ComplicationProvider(
mActivityStarter,
mComponentName,
mAppWidgetProvider,
mLayoutParams
);
final ArgumentCaptor<RemoteViews.InteractionHandler> creationCallbackCapture =
ArgumentCaptor.forClass(RemoteViews.InteractionHandler.class);
mComplicationProvider.onCreateComplication(mContext, mCreationCallback,
mInteractionCallback);
verify(mAppWidgetHostView, times(1))
.setInteractionHandler(creationCallbackCapture.capture());
mInteractionHandler = creationCallbackCapture.getValue();
}
@Test
public void testWidgetBringup() {
// Make sure widget was requested.
verify(mAppWidgetProvider, times(1)).getWidget(eq(mComponentName));
// Make sure widget was returned to callback.
verify(mCreationCallback, times(1)).onCreated(eq(mAppWidgetHostView),
eq(mLayoutParams));
}
@Test
public void testWidgetInteraction() {
// Trigger interaction.
mInteractionHandler.onInteraction(mAppWidgetHostView, mPendingIntent,
mRemoteResponse);
// Ensure activity is started.
verify(mActivityStarter, times(1))
.startPendingIntentDismissingKeyguard(eq(mPendingIntent), isNull(),
eq(mAppWidgetHostView));
// Verify exit is requested.
verify(mInteractionCallback, times(1)).onExit();
}
}