diff options
| author | 2022-05-23 21:52:02 +0000 | |
|---|---|---|
| committer | 2022-10-31 20:47:38 +0000 | |
| commit | 1037ad0c306dfd37299ac4b4eda11e1fb8e4cce5 (patch) | |
| tree | f6da736d83b443a2d9cddcd805e68571860b8489 | |
| parent | 1890e8adc1a529251d7e8a08a2cef5ceae360301 (diff) | |
Combining widget broadcasts
Combining broadcasts WIDGET_ENABLED and WIDGET_UPDATE into
WIDGET_ENABLE_AND_UPDATE to reduce response time at boot time
Test: Manually verified that some app widgets would function properly
Fix: 221890505
Change-Id: I6aff0f00464ec8628d2258d0d84dcd3c82258d57
(cherry picked from commit 1ae08fe75ebcfeab57b4fc3044bd69955044899e)
Merged-In: I6aff0f00464ec8628d2258d0d84dcd3c82258d57
8 files changed, 141 insertions, 44 deletions
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index a432b8dec2cb..fd9496947628 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -42,12 +42,15 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.util.DisplayMetrics; +import android.util.Log; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.os.BackgroundThread; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Updates AppWidget state; gets information about installed AppWidget providers and other @@ -63,6 +66,7 @@ import java.util.List; @RequiresFeature(PackageManager.FEATURE_APP_WIDGETS) public class AppWidgetManager { + /** * Activity action to launch from your {@link AppWidgetHost} activity when you want to * pick an AppWidget to display. The AppWidget picker activity will be launched. @@ -332,6 +336,17 @@ public class AppWidgetManager { public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; /** + * A combination broadcast of APPWIDGET_ENABLED and APPWIDGET_UPDATE. + * Sent during boot time and when the host is binding the widget for the very first time + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_APPWIDGET_ENABLE_AND_UPDATE = "android.appwidget.action" + + ".APPWIDGET_ENABLE_AND_UPDATE"; + + /** * Sent when the custom extras for an AppWidget change. * * <p class="note">This is a protected intent that can only be sent @@ -456,6 +471,8 @@ public class AppWidgetManager { public static final String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED"; + private static final String TAG = "AppWidgetManager"; + /** * An intent extra that contains multiple appWidgetIds. These are id values as * they were provided to the application during a recent restore from backup. It is @@ -511,6 +528,26 @@ public class AppWidgetManager { mPackageName = context.getOpPackageName(); mService = service; mDisplayMetrics = context.getResources().getDisplayMetrics(); + if (mService == null) { + return; + } + BackgroundThread.getExecutor().execute(() -> { + try { + mService.notifyProviderInheritance(getInstalledProvidersForPackage(mPackageName, + null) + .stream().filter(Objects::nonNull) + .map(info -> info.provider).filter(p -> { + try { + Class clazz = Class.forName(p.getClassName()); + return AppWidgetProvider.class.isAssignableFrom(clazz); + } catch (Exception e) { + return false; + } + }).toArray(ComponentName[]::new)); + } catch (Exception e) { + Log.e(TAG, "Nofity service of inheritance info", e); + } + }); } /** diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java index a5d2198a6e17..3344ebc083a2 100644 --- a/core/java/android/appwidget/AppWidgetProvider.java +++ b/core/java/android/appwidget/AppWidgetProvider.java @@ -58,7 +58,12 @@ public class AppWidgetProvider extends BroadcastReceiver { // Protect against rogue update broadcasts (not really a security issue, // just filter bad broacasts out so subclasses are less likely to crash). String action = intent.getAction(); - if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { + if (AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE.equals(action)) { + this.onReceive(context, new Intent(intent) + .setAction(AppWidgetManager.ACTION_APPWIDGET_ENABLED)); + this.onReceive(context, new Intent(intent) + .setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE)); + } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 6c689ff2b725..44997b4a9c30 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -578,6 +578,11 @@ public final class SystemUiDeviceConfigFlags { */ public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions"; + /** + * (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE + */ + public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f7467b5a9f86..7787b7c61593 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -142,6 +142,7 @@ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" /> <protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" /> + <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE" /> <protected-broadcast android:name="android.os.action.SETTING_RESTORED" /> diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 252dcfc5eb9e..4430bb4b3292 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -26,6 +26,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -254,11 +255,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private boolean mSafeMode; private int mMaxWidgetBitmapMemory; private boolean mIsProviderInfoPersisted; + private boolean mIsCombinedBroadcastEnabled; AppWidgetServiceImpl(Context context) { mContext = context; } + @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public void onStart() { mPackageManager = AppGlobals.getPackageManager(); mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); @@ -277,6 +280,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic() && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true); + mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true); if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) { Slog.d(TAG, "App widget provider info will not be persisted on this device"); } @@ -1123,16 +1128,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final int widgetCount = provider.widgets.size(); if (widgetCount == 1) { - // Tell the provider that it's ready. - sendEnableIntentLocked(provider); + // If we are binding the very first widget from a provider, we will send + // a combined broadcast or 2 separate broadcasts to tell the provider that + // it's ready, and we need them to provide the update now. + sendEnableAndUpdateIntentLocked(provider, new int[]{appWidgetId}); + } else { + // For any widget other then the first one, we just send update intent + // as we normally would. + sendUpdateIntentLocked(provider, new int[]{appWidgetId}); } - // Send an update now -- We need this update now, and just for this appWidgetId. - // It's less critical when the next one happens, so when we schedule the next one, - // we add updatePeriodMillis to its start time. That time will have some slop, - // but that's okay. - sendUpdateIntentLocked(provider, new int[] {appWidgetId}); - // Schedule the future updates. registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets)); @@ -2361,6 +2366,22 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku cancelBroadcastsLocked(provider); } + private void sendEnableAndUpdateIntentLocked(@NonNull Provider p, int[] appWidgetIds) { + final boolean canSendCombinedBroadcast = mIsCombinedBroadcastEnabled && p.info != null + && p.info.isExtendedFromAppWidgetProvider; + if (!canSendCombinedBroadcast) { + // If this function is called by mistake, send two separate broadcasts instead + sendEnableIntentLocked(p); + sendUpdateIntentLocked(p, appWidgetIds); + return; + } + + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.id.componentName); + sendBroadcastAsUser(intent, p.id.getProfile()); + } + private void sendEnableIntentLocked(Provider p) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); intent.setComponent(p.id.componentName); @@ -2852,7 +2873,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (provider.widgets.size() > 0) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "appwidget init " + provider.id.componentName.getPackageName()); - sendEnableIntentLocked(provider); provider.widgets.forEach(widget -> { widget.trackingUpdate = true; Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, @@ -2861,7 +2881,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Log.i(TAG, "Widget update scheduled on unlock " + widget.toString()); }); int[] appWidgetIds = getWidgetIds(provider.widgets); - sendUpdateIntentLocked(provider, appWidgetIds); + sendEnableAndUpdateIntentLocked(provider, appWidgetIds); registerForBroadcastsLocked(provider, appWidgetIds); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java index 7610b7ca5ec3..b33e22fe6d1e 100644 --- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java @@ -164,7 +164,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { } public void testRequestPinAppWidget() { - ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class); + ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class); // Set up users. when(mMockShortcutService.requestPinAppWidget(anyString(), any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt())) @@ -289,6 +289,16 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { assertEquals(4, updates.size()); } + public void testReceiveBroadcastBehavior_enableAndUpdate() { + TestAppWidgetProvider testAppWidgetProvider = new TestAppWidgetProvider(); + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE); + + testAppWidgetProvider.onReceive(mTestContext, intent); + + assertTrue(testAppWidgetProvider.isBehaviorSuccess()); + } + + public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() { int widgetId = setupHostAndWidget(); int widgetId2 = bindNewWidget(); @@ -385,7 +395,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { } private int bindNewWidget() { - ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class); + ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class); int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID); assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider)); assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider); diff --git a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java b/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java deleted file mode 100644 index fd99b21c9538..000000000000 --- a/services/tests/servicestests/src/com/android/server/appwidget/DummyAppWidget.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2017 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.server.appwidget; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -/** - * Placeholder widget for testing - */ -public class DummyAppWidget extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - } -} diff --git a/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java new file mode 100644 index 000000000000..6c11a6829c12 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appwidget/TestAppWidgetProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 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.server.appwidget; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; + +/** + * Placeholder widget for testing + */ +public class TestAppWidgetProvider extends AppWidgetProvider { + private boolean mEnabled; + private boolean mUpdated; + + TestAppWidgetProvider() { + super(); + mEnabled = false; + mUpdated = false; + } + + public boolean isBehaviorSuccess() { + return mEnabled && mUpdated; + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, + int[] appWidgetids) { + mUpdated = true; + } + + @Override + public void onEnabled(Context context) { + mEnabled = true; + } +} |