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.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.
@@ -331,6 +335,17 @@ public class AppWidgetManager {
@BroadcastBehavior(explicitOnly = true)
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.
*
@@ -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);
}
});
}
/**

View File

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

View File

@@ -567,6 +567,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() {
}
}

View File

@@ -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" />

View File

@@ -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;
@@ -253,11 +254,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);
@@ -273,6 +276,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");
}
@@ -1092,16 +1097,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));
@@ -2332,6 +2337,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);
@@ -2823,7 +2844,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,
@@ -2832,7 +2852,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);
}

View File

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

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