summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayProvider.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/AppWidgetOverlayComponent.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/AppWidgetOverlayProviderTest.java136
7 files changed, 393 insertions, 0 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 25b73ab401a8..26a49623fabf 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -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" />
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 799c92c6ce18..4cecb3916f03 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -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,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayProvider.java
new file mode 100644
index 000000000000..a635d3f740cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayProvider.java
@@ -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);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java
new file mode 100644
index 000000000000..d1da1e691ed6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java
@@ -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;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/AppWidgetOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/AppWidgetOverlayComponent.java
new file mode 100644
index 000000000000..3103057be209
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/AppWidgetOverlayComponent.java
@@ -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();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
new file mode 100644
index 000000000000..7bf2361e471c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -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 {
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/AppWidgetOverlayProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/AppWidgetOverlayProviderTest.java
new file mode 100644
index 000000000000..504ba3dde3b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/AppWidgetOverlayProviderTest.java
@@ -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();
+ }
+}