diff options
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(); + } +} |