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
This commit is contained in:
Sihua Ma
2022-05-23 21:52:02 +00:00
parent b12d9ae7f6
commit 1ae08fe75e
8 changed files with 141 additions and 44 deletions

View File

@@ -42,12 +42,15 @@ import android.os.Handler;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.os.BackgroundThread;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Updates AppWidget state; gets information about installed AppWidget providers and other * Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -63,6 +66,7 @@ import java.util.List;
@RequiresFeature(PackageManager.FEATURE_APP_WIDGETS) @RequiresFeature(PackageManager.FEATURE_APP_WIDGETS)
public class AppWidgetManager { public class AppWidgetManager {
/** /**
* Activity action to launch from your {@link AppWidgetHost} activity when you want to * 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. * pick an AppWidget to display. The AppWidget picker activity will be launched.
@@ -331,6 +335,17 @@ public class AppWidgetManager {
@BroadcastBehavior(explicitOnly = true) @BroadcastBehavior(explicitOnly = true)
public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; 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. * Sent when the custom extras for an AppWidget change.
* *
@@ -456,6 +471,8 @@ public class AppWidgetManager {
public static final String ACTION_APPWIDGET_HOST_RESTORED public static final String ACTION_APPWIDGET_HOST_RESTORED
= "android.appwidget.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 * 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 * they were provided to the application during a recent restore from backup. It is
@@ -511,6 +528,26 @@ public class AppWidgetManager {
mPackageName = context.getOpPackageName(); mPackageName = context.getOpPackageName();
mService = service; mService = service;
mDisplayMetrics = context.getResources().getDisplayMetrics(); 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);
}
});
} }
/** /**

View File

@@ -58,7 +58,12 @@ public class AppWidgetProvider extends BroadcastReceiver {
// Protect against rogue update broadcasts (not really a security issue, // Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash). // just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction(); 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(); Bundle extras = intent.getExtras();
if (extras != null) { if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);

View File

@@ -567,6 +567,11 @@ public final class SystemUiDeviceConfigFlags {
*/ */
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions"; 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() { private SystemUiDeviceConfigFlags() {
} }
} }

View File

@@ -142,6 +142,7 @@
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" /> <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_HOST_RESTORED" />
<protected-broadcast android:name="android.appwidget.action.APPWIDGET_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" /> <protected-broadcast android:name="android.os.action.SETTING_RESTORED" />

View File

@@ -26,6 +26,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
@@ -253,11 +254,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private boolean mSafeMode; private boolean mSafeMode;
private int mMaxWidgetBitmapMemory; private int mMaxWidgetBitmapMemory;
private boolean mIsProviderInfoPersisted; private boolean mIsProviderInfoPersisted;
private boolean mIsCombinedBroadcastEnabled;
AppWidgetServiceImpl(Context context) { AppWidgetServiceImpl(Context context) {
mContext = context; mContext = context;
} }
@RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG)
public void onStart() { public void onStart() {
mPackageManager = AppGlobals.getPackageManager(); mPackageManager = AppGlobals.getPackageManager();
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
@@ -273,6 +276,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic() mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
&& DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true); SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) { if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
Slog.d(TAG, "App widget provider info will not be persisted on this device"); Slog.d(TAG, "App widget provider info will not be persisted on this device");
} }
@@ -1092,15 +1097,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
final int widgetCount = provider.widgets.size(); final int widgetCount = provider.widgets.size();
if (widgetCount == 1) { if (widgetCount == 1) {
// Tell the provider that it's ready. // If we are binding the very first widget from a provider, we will send
sendEnableIntentLocked(provider); // 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});
// Send an update now -- We need this update now, and just for this appWidgetId. } else {
// It's less critical when the next one happens, so when we schedule the next one, // For any widget other then the first one, we just send update intent
// we add updatePeriodMillis to its start time. That time will have some slop, // as we normally would.
// but that's okay.
sendUpdateIntentLocked(provider, new int[]{appWidgetId}); sendUpdateIntentLocked(provider, new int[]{appWidgetId});
}
// Schedule the future updates. // Schedule the future updates.
registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets)); registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
@@ -2332,6 +2337,22 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
cancelBroadcastsLocked(provider); 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) { private void sendEnableIntentLocked(Provider p) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
intent.setComponent(p.id.componentName); intent.setComponent(p.id.componentName);
@@ -2823,7 +2844,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
if (provider.widgets.size() > 0) { if (provider.widgets.size() > 0) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"appwidget init " + provider.id.componentName.getPackageName()); "appwidget init " + provider.id.componentName.getPackageName());
sendEnableIntentLocked(provider);
provider.widgets.forEach(widget -> { provider.widgets.forEach(widget -> {
widget.trackingUpdate = true; widget.trackingUpdate = true;
Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -2832,7 +2852,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
Log.i(TAG, "Widget update scheduled on unlock " + widget.toString()); Log.i(TAG, "Widget update scheduled on unlock " + widget.toString());
}); });
int[] appWidgetIds = getWidgetIds(provider.widgets); int[] appWidgetIds = getWidgetIds(provider.widgets);
sendUpdateIntentLocked(provider, appWidgetIds); sendEnableAndUpdateIntentLocked(provider, appWidgetIds);
registerForBroadcastsLocked(provider, appWidgetIds); registerForBroadcastsLocked(provider, appWidgetIds);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} }

View File

@@ -164,7 +164,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
} }
public void testRequestPinAppWidget() { public void testRequestPinAppWidget() {
ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class); ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
// Set up users. // Set up users.
when(mMockShortcutService.requestPinAppWidget(anyString(), when(mMockShortcutService.requestPinAppWidget(anyString(),
any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt())) any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt()))
@@ -289,6 +289,16 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
assertEquals(4, updates.size()); 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() { public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
int widgetId = setupHostAndWidget(); int widgetId = setupHostAndWidget();
int widgetId2 = bindNewWidget(); int widgetId2 = bindNewWidget();
@@ -385,7 +395,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
} }
private int bindNewWidget() { private int bindNewWidget() {
ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class); ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID); int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider)); assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider); assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);

View File

@@ -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) {
}
}

View File

@@ -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;
}
}