summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java6
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java60
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java131
-rw-r--r--services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java92
4 files changed, 287 insertions, 2 deletions
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index f19bfc669997..c94438e3cee8 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -536,6 +536,12 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
+ /**
+ * (boolean) Whether widget provider info would be saved to / loaded from system persistence
+ * layer as opposed to individual manifests in respective apps.
+ */
+ public static final String PERSISTS_WIDGET_PROVIDER_INFO = "persists_widget_provider_info";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index d7554cc42749..bc4b2a6f5247 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -20,9 +20,11 @@ import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.res.Resources.ID_NULL;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -83,6 +85,7 @@ import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.service.appwidget.AppWidgetServiceDumpProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
@@ -113,6 +116,7 @@ import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -150,6 +154,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private static final String TAG = "AppWidgetServiceImpl";
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_PROVIDER_INFO_CACHE = true;
private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
@@ -246,6 +251,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private boolean mSafeMode;
private int mMaxWidgetBitmapMemory;
+ private boolean mIsProviderInfoPersisted;
AppWidgetServiceImpl(Context context) {
mContext = context;
@@ -263,6 +269,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
mBackupRestoreController = new BackupRestoreController();
mSecurityPolicy = new SecurityPolicy();
+ mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
+ && DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.PERSISTS_WIDGET_PROVIDER_INFO, true);
+ if (DEBUG_PROVIDER_INFO_CACHE && !mIsProviderInfoPersisted) {
+ Slog.d(TAG, "App widget provider info will not be persisted on this device");
+ }
computeMaximumWidgetBitmapMemory();
registerBroadcastReceiver();
@@ -607,10 +619,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ @GuardedBy("mLock")
private void ensureGroupStateLoadedLocked(int userId) {
ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */ true );
}
+ @GuardedBy("mLock")
private void ensureGroupStateLoadedLocked(int userId, boolean enforceUserUnlockingOrUnlocked) {
if (enforceUserUnlockingOrUnlocked && !isUserRunningAndUnlocked(userId)) {
throw new IllegalStateException(
@@ -2184,6 +2198,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ @GuardedBy("mLock")
private void loadGroupWidgetProvidersLocked(int[] profileIds) {
List<ResolveInfo> allReceivers = null;
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
@@ -2409,7 +2424,24 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
- private static void serializeProvider(TypedXmlSerializer out, Provider p) throws IOException {
+ private static void serializeProvider(
+ @NonNull final TypedXmlSerializer out, @NonNull final Provider p) throws IOException {
+ Objects.requireNonNull(out);
+ Objects.requireNonNull(p);
+ serializeProviderInner(out, p, false /* persistsProviderInfo */);
+ }
+
+ private static void serializeProviderWithProviderInfo(
+ @NonNull final TypedXmlSerializer out, @NonNull final Provider p) throws IOException {
+ Objects.requireNonNull(out);
+ Objects.requireNonNull(p);
+ serializeProviderInner(out, p, true /* persistsProviderInfo */);
+ }
+
+ private static void serializeProviderInner(@NonNull final TypedXmlSerializer out,
+ @NonNull final Provider p, final boolean persistsProviderInfo) throws IOException {
+ Objects.requireNonNull(out);
+ Objects.requireNonNull(p);
out.startTag(null, "p");
out.attribute(null, "pkg", p.id.componentName.getPackageName());
out.attribute(null, "cl", p.id.componentName.getClassName());
@@ -2417,6 +2449,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
if (!TextUtils.isEmpty(p.infoTag)) {
out.attribute(null, "info_tag", p.infoTag);
}
+ if (DEBUG_PROVIDER_INFO_CACHE && persistsProviderInfo && !p.mInfoParsed) {
+ Slog.d(TAG, "Provider info from " + p.id.componentName + " won't be persisted.");
+ }
+ if (persistsProviderInfo && p.mInfoParsed) {
+ AppWidgetXmlUtil.writeAppWidgetProviderInfoLocked(out, p.info);
+ }
out.endTag(null, "p");
}
@@ -2768,6 +2806,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
// only call from initialization -- it assumes that the data structures are all empty
+ @GuardedBy("mLock")
private void loadGroupStateLocked(int[] profileIds) {
// We can bind the widgets to host and providers only after
// reading the host and providers for all users since a widget
@@ -2959,6 +2998,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
return false;
}
+ @GuardedBy("mLock")
private void saveStateLocked(int userId) {
tagProvidersAndHosts();
@@ -3012,6 +3052,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ @GuardedBy("mLock")
private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
int N;
@@ -3028,7 +3069,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
if (provider.getUserId() != userId) {
continue;
}
- if (provider.shouldBePersisted()) {
+ if (mIsProviderInfoPersisted) {
+ serializeProviderWithProviderInfo(out, provider);
+ } else if (provider.shouldBePersisted()) {
serializeProvider(out, provider);
}
}
@@ -3074,6 +3117,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
+ @GuardedBy("mLock")
private int readProfileStateFromFileLocked(FileInputStream stream, int userId,
List<LoadedWidgetState> outLoadedWidgets) {
int version = -1;
@@ -3127,6 +3171,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
provider.zombie = true;
provider.id = providerId;
mProviders.add(provider);
+ } else if (mIsProviderInfoPersisted) {
+ final AppWidgetProviderInfo info =
+ AppWidgetXmlUtil.readAppWidgetProviderInfoLocked(parser);
+ if (DEBUG_PROVIDER_INFO_CACHE && info == null) {
+ Slog.d(TAG, "Unable to load widget provider info from xml for "
+ + providerId.componentName);
+ }
+ if (info != null) {
+ info.provider = providerId.componentName;
+ info.providerInfo = providerInfo;
+ provider.setInfoLocked(info);
+ }
}
final int providerTag = parser.getAttributeIntHex(null, "tag",
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
new file mode 100644
index 000000000000..297575ca168f
--- /dev/null
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class AppWidgetXmlUtil {
+
+ private static final String ATTR_MIN_WIDTH = "min_width";
+ private static final String ATTR_MIN_HEIGHT = "min_height";
+ private static final String ATTR_MIN_RESIZE_WIDTH = "min_resize_width";
+ private static final String ATTR_MIN_RESIZE_HEIGHT = "min_resize_height";
+ private static final String ATTR_MAX_RESIZE_WIDTH = "max_resize_width";
+ private static final String ATTR_MAX_RESIZE_HEIGHT = "max_resize_height";
+ private static final String ATTR_TARGET_CELL_WIDTH = "target_cell_width";
+ private static final String ATTR_TARGET_CELL_HEIGHT = "target_cell_height";
+ private static final String ATTR_UPDATE_PERIOD_MILLIS = "update_period_millis";
+ private static final String ATTR_INITIAL_LAYOUT = "initial_layout";
+ private static final String ATTR_INITIAL_KEYGUARD_LAYOUT = "initial_keyguard_layout";
+ private static final String ATTR_CONFIGURE = "configure";
+ private static final String ATTR_LABEL = "label";
+ private static final String ATTR_ICON = "icon";
+ private static final String ATTR_PREVIEW_IMAGE = "preview_image";
+ private static final String ATTR_PREVIEW_LAYOUT = "preview_layout";
+ private static final String ATTR_AUTO_ADVANCED_VIEW_ID = "auto_advance_view_id";
+ private static final String ATTR_RESIZE_MODE = "resize_mode";
+ private static final String ATTR_WIDGET_CATEGORY = "widget_category";
+ private static final String ATTR_WIDGET_FEATURES = "widget_features";
+ private static final String ATTR_DESCRIPTION_RES = "description_res";
+ private static final String ATTR_OS_FINGERPRINT = "os_fingerprint";
+
+ /**
+ * @hide
+ */
+ public static void writeAppWidgetProviderInfoLocked(@NonNull final TypedXmlSerializer out,
+ @NonNull final AppWidgetProviderInfo info) throws IOException {
+ Objects.requireNonNull(out);
+ Objects.requireNonNull(info);
+ out.attributeInt(null, ATTR_MIN_WIDTH, info.minWidth);
+ out.attributeInt(null, ATTR_MIN_HEIGHT, info.minHeight);
+ out.attributeInt(null, ATTR_MIN_RESIZE_WIDTH, info.minResizeWidth);
+ out.attributeInt(null, ATTR_MIN_RESIZE_HEIGHT, info.minResizeHeight);
+ out.attributeInt(null, ATTR_MAX_RESIZE_WIDTH, info.maxResizeWidth);
+ out.attributeInt(null, ATTR_MAX_RESIZE_HEIGHT, info.maxResizeHeight);
+ out.attributeInt(null, ATTR_TARGET_CELL_WIDTH, info.targetCellWidth);
+ out.attributeInt(null, ATTR_TARGET_CELL_HEIGHT, info.targetCellHeight);
+ out.attributeInt(null, ATTR_UPDATE_PERIOD_MILLIS, info.updatePeriodMillis);
+ out.attributeInt(null, ATTR_INITIAL_LAYOUT, info.initialLayout);
+ out.attributeInt(null, ATTR_INITIAL_KEYGUARD_LAYOUT, info.initialKeyguardLayout);
+ if (info.configure != null) {
+ out.attribute(null, ATTR_CONFIGURE, info.configure.flattenToShortString());
+ }
+ out.attribute(null, ATTR_LABEL, info.label);
+ out.attributeInt(null, ATTR_ICON, info.icon);
+ out.attributeInt(null, ATTR_PREVIEW_IMAGE, info.previewImage);
+ out.attributeInt(null, ATTR_PREVIEW_LAYOUT, info.previewLayout);
+ out.attributeInt(null, ATTR_AUTO_ADVANCED_VIEW_ID, info.autoAdvanceViewId);
+ out.attributeInt(null, ATTR_RESIZE_MODE, info.resizeMode);
+ out.attributeInt(null, ATTR_WIDGET_CATEGORY, info.widgetCategory);
+ out.attributeInt(null, ATTR_WIDGET_FEATURES, info.widgetFeatures);
+ out.attributeInt(null, ATTR_DESCRIPTION_RES, info.descriptionRes);
+ out.attribute(null, ATTR_OS_FINGERPRINT, Build.FINGERPRINT);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static AppWidgetProviderInfo readAppWidgetProviderInfoLocked(
+ @NonNull final TypedXmlPullParser parser) {
+ Objects.requireNonNull(parser);
+ final String fingerprint = parser.getAttributeValue(null, ATTR_OS_FINGERPRINT);
+ if (!Build.FINGERPRINT.equals(fingerprint)) {
+ return null;
+ }
+ final AppWidgetProviderInfo info = new AppWidgetProviderInfo();
+ info.minWidth = parser.getAttributeInt(null, ATTR_MIN_WIDTH, 0);
+ info.minHeight = parser.getAttributeInt(null, ATTR_MIN_HEIGHT, 0);
+ info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_WIDTH, 0);
+ info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
+ info.maxResizeWidth = parser.getAttributeInt(null, ATTR_MAX_RESIZE_WIDTH, 0);
+ info.maxResizeHeight = parser.getAttributeInt(null, ATTR_MAX_RESIZE_HEIGHT, 0);
+ info.targetCellWidth = parser.getAttributeInt(null, ATTR_TARGET_CELL_WIDTH, 0);
+ info.targetCellHeight = parser.getAttributeInt(null, ATTR_TARGET_CELL_HEIGHT, 0);
+ info.updatePeriodMillis = parser.getAttributeInt(null, ATTR_UPDATE_PERIOD_MILLIS, 0);
+ info.initialLayout = parser.getAttributeInt(null, ATTR_INITIAL_LAYOUT, 0);
+ info.initialKeyguardLayout = parser.getAttributeInt(
+ null, ATTR_INITIAL_KEYGUARD_LAYOUT, 0);
+ final String configure = parser.getAttributeValue(null, ATTR_CONFIGURE);
+ if (!TextUtils.isEmpty(configure)) {
+ info.configure = ComponentName.unflattenFromString(configure);
+ }
+ info.label = parser.getAttributeValue(null, ATTR_LABEL);
+ info.icon = parser.getAttributeInt(null, ATTR_ICON, 0);
+ info.previewImage = parser.getAttributeInt(null, ATTR_PREVIEW_IMAGE, 0);
+ info.previewLayout = parser.getAttributeInt(null, ATTR_PREVIEW_LAYOUT, 0);
+ info.autoAdvanceViewId = parser.getAttributeInt(null, ATTR_AUTO_ADVANCED_VIEW_ID, 0);
+ info.resizeMode = parser.getAttributeInt(null, ATTR_RESIZE_MODE, 0);
+ info.widgetCategory = parser.getAttributeInt(null, ATTR_WIDGET_CATEGORY, 0);
+ info.widgetFeatures = parser.getAttributeInt(null, ATTR_WIDGET_FEATURES, 0);
+ info.descriptionRes = parser.getAttributeInt(null, ATTR_DESCRIPTION_RES, 0);
+ return info;
+ }
+}
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 ff8fedce9368..7610b7ca5ec3 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -28,6 +28,9 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManagerInternal;
import android.app.admin.DevicePolicyManagerInternal;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetManagerInternal;
@@ -39,11 +42,16 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutServiceInternal;
import android.os.Handler;
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.AtomicFile;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
import android.widget.RemoteViews;
import com.android.frameworks.servicestests.R;
@@ -51,9 +59,16 @@ import com.android.internal.appwidget.IAppWidgetHost;
import com.android.server.LocalServices;
import org.mockito.ArgumentCaptor;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@@ -77,6 +92,8 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
private AppWidgetManager mManager;
private ShortcutServiceInternal mMockShortcutService;
+ private PackageManagerInternal mMockPackageManager;
+ private AppOpsManagerInternal mMockAppOpsManagerInternal;
private IAppWidgetHost mMockHost;
@Override
@@ -85,6 +102,8 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
LocalServices.removeServiceForTest(AppWidgetManagerInternal.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.removeServiceForTest(AppOpsManagerInternal.class);
mTestContext = new TestContext();
mPkgName = mTestContext.getOpPackageName();
@@ -92,9 +111,16 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
mManager = new AppWidgetManager(mTestContext, mService);
mMockShortcutService = mock(ShortcutServiceInternal.class);
+ mMockPackageManager = mock(PackageManagerInternal.class);
+ mMockAppOpsManagerInternal = mock(AppOpsManagerInternal.class);
mMockHost = mock(IAppWidgetHost.class);
LocalServices.addService(ShortcutServiceInternal.class, mMockShortcutService);
+ LocalServices.addService(PackageManagerInternal.class, mMockPackageManager);
+ LocalServices.addService(AppOpsManagerInternal.class, mMockAppOpsManagerInternal);
+ when(mMockPackageManager.filterAppAccess(anyString(), anyInt(), anyInt()))
+ .thenReturn(false);
mService.onStart();
+ mService.systemServicesReady();
}
public void testLoadDescription() {
@@ -323,6 +349,34 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
assertThat(info.previewLayout).isEqualTo(R.layout.widget_preview);
}
+ public void testWidgetProviderInfoPersistence() throws IOException {
+ final AppWidgetProviderInfo original = new AppWidgetProviderInfo();
+ original.minWidth = 40;
+ original.minHeight = 40;
+ original.maxResizeWidth = 250;
+ original.maxResizeHeight = 120;
+ original.targetCellWidth = 1;
+ original.targetCellHeight = 1;
+ original.updatePeriodMillis = 86400000;
+ original.previewLayout = R.layout.widget_preview;
+ original.label = "test";
+
+ final File file = new File(mTestContext.getDataDir(), "appwidget_provider_info.xml");
+ saveWidgetProviderInfoLocked(file, original);
+ final AppWidgetProviderInfo target = loadAppWidgetProviderInfoLocked(file);
+
+ assertThat(target.minWidth).isEqualTo(original.minWidth);
+ assertThat(target.minHeight).isEqualTo(original.minHeight);
+ assertThat(target.minResizeWidth).isEqualTo(original.minResizeWidth);
+ assertThat(target.minResizeHeight).isEqualTo(original.minResizeHeight);
+ assertThat(target.maxResizeWidth).isEqualTo(original.maxResizeWidth);
+ assertThat(target.maxResizeHeight).isEqualTo(original.maxResizeHeight);
+ assertThat(target.targetCellWidth).isEqualTo(original.targetCellWidth);
+ assertThat(target.targetCellHeight).isEqualTo(original.targetCellHeight);
+ assertThat(target.updatePeriodMillis).isEqualTo(original.updatePeriodMillis);
+ assertThat(target.previewLayout).isEqualTo(original.previewLayout);
+ }
+
private int setupHostAndWidget() {
List<PendingHostUpdate> updates = mService.startListening(
mMockHost, mPkgName, HOST_ID, new int[0]).getList();
@@ -353,6 +407,44 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
return mTestContext.getResources().getInteger(resId);
}
+ private static void saveWidgetProviderInfoLocked(@NonNull final File dst,
+ @Nullable final AppWidgetProviderInfo info)
+ throws IOException {
+ Objects.requireNonNull(dst);
+ if (info == null) {
+ return;
+ }
+ final AtomicFile file = new AtomicFile(dst);
+ final FileOutputStream stream = file.startWrite();
+ final TypedXmlSerializer out = Xml.resolveSerializer(stream);
+ out.startDocument(null, true);
+ out.startTag(null, "p");
+ AppWidgetXmlUtil.writeAppWidgetProviderInfoLocked(out, info);
+ out.endTag(null, "p");
+ out.endDocument();
+ file.finishWrite(stream);
+ }
+
+ public static AppWidgetProviderInfo loadAppWidgetProviderInfoLocked(@NonNull final File dst) {
+ Objects.requireNonNull(dst);
+ final AtomicFile file = new AtomicFile(dst);
+ try (FileInputStream stream = file.openRead()) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // drain whitespace, comments, etc.
+ }
+ final String nodeName = parser.getName();
+ if (!"p".equals(nodeName)) {
+ return null;
+ }
+ return AppWidgetXmlUtil.readAppWidgetProviderInfoLocked(parser);
+ } catch (IOException | XmlPullParserException e) {
+ return null;
+ }
+ }
+
private class TestContext extends ContextWrapper {
public TestContext() {