Add Support for App Widgets as Overlays.
This changelist adds support for loading App Widgets from a ComponentName. This functionality is wrapped in an OverlayProvider for integration with DreamOverlayService. Bug: 201675932 Test: atest AppWidgetOverlayProviderTest Change-Id: Iddc6297ee0a9eb6c945a70368ba1f1b7ef0b1a0b
This commit is contained in:
@@ -294,6 +294,11 @@
|
||||
|
||||
<uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
|
||||
|
||||
<!-- Permission for dream overlay. -->
|
||||
<uses-permission android:name="android.permission.BIND_DREAM_SERVICE" />
|
||||
|
||||
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
|
||||
|
||||
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
|
||||
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
|
||||
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.android.systemui.controls.dagger.ControlsModule;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.demomode.dagger.DemoModeModule;
|
||||
import com.android.systemui.doze.dagger.DozeComponent;
|
||||
import com.android.systemui.dreams.dagger.DreamModule;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlagManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
@@ -101,6 +102,7 @@ import dagger.Provides;
|
||||
AssistModule.class,
|
||||
ClockModule.class,
|
||||
CommunalModule.class,
|
||||
DreamModule.class,
|
||||
ControlsModule.class,
|
||||
DemoModeModule.class,
|
||||
FalsingModule.class,
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.OverlayHost;
|
||||
import com.android.systemui.dreams.OverlayHostView;
|
||||
import com.android.systemui.dreams.OverlayProvider;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* {@link AppWidgetOverlayProvider} is an implementation of {@link OverlayProvider} for providing
|
||||
* app widget-based overlays.
|
||||
*/
|
||||
public class AppWidgetOverlayProvider implements OverlayProvider {
|
||||
private static final String TAG = "AppWdgtOverlayProvider";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final ActivityStarter mActivityStarter;
|
||||
private final AppWidgetProvider mAppWidgetProvider;
|
||||
private final ComponentName mComponentName;
|
||||
private final OverlayHostView.LayoutParams mLayoutParams;
|
||||
|
||||
@Inject
|
||||
public AppWidgetOverlayProvider(ActivityStarter activityStarter,
|
||||
ComponentName componentName, AppWidgetProvider widgetProvider,
|
||||
OverlayHostView.LayoutParams layoutParams) {
|
||||
mActivityStarter = activityStarter;
|
||||
mComponentName = componentName;
|
||||
mAppWidgetProvider = widgetProvider;
|
||||
mLayoutParams = layoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOverlay(Context context, OverlayHost.CreationCallback creationCallback,
|
||||
OverlayHost.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 App Widget Host across potentially multiple
|
||||
* {@link AppWidgetOverlayProvider} 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.dagger;
|
||||
|
||||
import android.content.ComponentName;
|
||||
|
||||
import com.android.systemui.dreams.OverlayHostView;
|
||||
import com.android.systemui.dreams.appwidgets.AppWidgetOverlayProvider;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
|
||||
/** */
|
||||
@Subcomponent
|
||||
public interface AppWidgetOverlayComponent {
|
||||
/** */
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
AppWidgetOverlayComponent build(@BindsInstance ComponentName component,
|
||||
@BindsInstance OverlayHostView.LayoutParams layoutParams);
|
||||
}
|
||||
|
||||
/** Builds a {@link AppWidgetOverlayProvider}. */
|
||||
AppWidgetOverlayProvider getAppWidgetOverlayProvider();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.dagger;
|
||||
|
||||
import dagger.Module;
|
||||
|
||||
/**
|
||||
* Dagger Module providing Communal-related functionality.
|
||||
*/
|
||||
@Module(subcomponents = {
|
||||
AppWidgetOverlayComponent.class,
|
||||
})
|
||||
public interface DreamModule {
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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.appwidgets.AppWidgetOverlayProvider;
|
||||
import com.android.systemui.dreams.appwidgets.AppWidgetProvider;
|
||||
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 AppWidgetOverlayProviderTest extends SysuiTestCase {
|
||||
@Mock
|
||||
ActivityStarter mActivityStarter;
|
||||
|
||||
@Mock
|
||||
ComponentName mComponentName;
|
||||
|
||||
@Mock
|
||||
AppWidgetProvider mAppWidgetProvider;
|
||||
|
||||
@Mock
|
||||
AppWidgetHostView mAppWidgetHostView;
|
||||
|
||||
@Mock
|
||||
OverlayHost.CreationCallback mCreationCallback;
|
||||
|
||||
@Mock
|
||||
OverlayHost.InteractionCallback mInteractionCallback;
|
||||
|
||||
@Mock
|
||||
PendingIntent mPendingIntent;
|
||||
|
||||
@Mock
|
||||
RemoteViews.RemoteResponse mRemoteResponse;
|
||||
|
||||
AppWidgetOverlayProvider mOverlayProvider;
|
||||
|
||||
RemoteViews.InteractionHandler mInteractionHandler;
|
||||
|
||||
@Rule
|
||||
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
|
||||
|
||||
@Rule
|
||||
public SysuiTestableContext mContext = new SysuiTestableContext(
|
||||
InstrumentationRegistry.getContext(), mLeakCheck);
|
||||
|
||||
OverlayHostView.LayoutParams mLayoutParams = new OverlayHostView.LayoutParams(
|
||||
OverlayHostView.LayoutParams.MATCH_PARENT, OverlayHostView.LayoutParams.MATCH_PARENT);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mPendingIntent.isActivity()).thenReturn(true);
|
||||
when(mAppWidgetProvider.getWidget(mComponentName)).thenReturn(mAppWidgetHostView);
|
||||
|
||||
mOverlayProvider = new AppWidgetOverlayProvider(
|
||||
mActivityStarter,
|
||||
mComponentName,
|
||||
mAppWidgetProvider,
|
||||
mLayoutParams
|
||||
);
|
||||
|
||||
final ArgumentCaptor<RemoteViews.InteractionHandler> creationCallbackCapture =
|
||||
ArgumentCaptor.forClass(RemoteViews.InteractionHandler.class);
|
||||
|
||||
mOverlayProvider.onCreateOverlay(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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user