Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a710d8fe authored by Pinyao Ting's avatar Pinyao Ting Committed by Android (Google) Code Review
Browse files

Merge "Implement extra persistence layer for widget provider info" into tm-dev

parents 4e97fd89 09ddda9f
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -536,6 +536,12 @@ public final class SystemUiDeviceConfigFlags {
     */
     */
    public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
    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() {
    private SystemUiDeviceConfigFlags() {
    }
    }
}
}
+58 −2
Original line number Original line Diff line number Diff line
@@ -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_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.res.Resources.ID_NULL;
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 static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;


import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal;
@@ -83,6 +85,7 @@ import android.os.SystemClock;
import android.os.Trace;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.service.appwidget.AppWidgetServiceDumpProto;
import android.service.appwidget.AppWidgetServiceDumpProto;
import android.service.appwidget.WidgetProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
import android.text.TextUtils;
@@ -113,6 +116,7 @@ import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
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 String TAG = "AppWidgetServiceImpl";


    private static final boolean DEBUG = false;
    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 OLD_KEYGUARD_HOST_PACKAGE = "android";
    private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
    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 boolean mSafeMode;
    private int mMaxWidgetBitmapMemory;
    private int mMaxWidgetBitmapMemory;
    private boolean mIsProviderInfoPersisted;


    AppWidgetServiceImpl(Context context) {
    AppWidgetServiceImpl(Context context) {
        mContext = context;
        mContext = context;
@@ -263,6 +269,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
        mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
        mBackupRestoreController = new BackupRestoreController();
        mBackupRestoreController = new BackupRestoreController();
        mSecurityPolicy = new SecurityPolicy();
        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();
        computeMaximumWidgetBitmapMemory();
        registerBroadcastReceiver();
        registerBroadcastReceiver();
@@ -607,10 +619,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
        }
    }
    }


    @GuardedBy("mLock")
    private void ensureGroupStateLoadedLocked(int userId) {
    private void ensureGroupStateLoadedLocked(int userId) {
        ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */ true );
        ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */ true );
    }
    }


    @GuardedBy("mLock")
    private void ensureGroupStateLoadedLocked(int userId, boolean enforceUserUnlockingOrUnlocked) {
    private void ensureGroupStateLoadedLocked(int userId, boolean enforceUserUnlockingOrUnlocked) {
        if (enforceUserUnlockingOrUnlocked && !isUserRunningAndUnlocked(userId)) {
        if (enforceUserUnlockingOrUnlocked && !isUserRunningAndUnlocked(userId)) {
            throw new IllegalStateException(
            throw new IllegalStateException(
@@ -2184,6 +2198,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
        }
    }
    }


    @GuardedBy("mLock")
    private void loadGroupWidgetProvidersLocked(int[] profileIds) {
    private void loadGroupWidgetProvidersLocked(int[] profileIds) {
        List<ResolveInfo> allReceivers = null;
        List<ResolveInfo> allReceivers = null;
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        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.startTag(null, "p");
        out.attribute(null, "pkg", p.id.componentName.getPackageName());
        out.attribute(null, "pkg", p.id.componentName.getPackageName());
        out.attribute(null, "cl", p.id.componentName.getClassName());
        out.attribute(null, "cl", p.id.componentName.getClassName());
@@ -2417,6 +2449,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        if (!TextUtils.isEmpty(p.infoTag)) {
        if (!TextUtils.isEmpty(p.infoTag)) {
            out.attribute(null, "info_tag", 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");
        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
    // only call from initialization -- it assumes that the data structures are all empty
    @GuardedBy("mLock")
    private void loadGroupStateLocked(int[] profileIds) {
    private void loadGroupStateLocked(int[] profileIds) {
        // We can bind the widgets to host and providers only after
        // We can bind the widgets to host and providers only after
        // reading the host and providers for all users since a widget
        // reading the host and providers for all users since a widget
@@ -2959,6 +2998,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        return false;
        return false;
    }
    }


    @GuardedBy("mLock")
    private void saveStateLocked(int userId) {
    private void saveStateLocked(int userId) {
        tagProvidersAndHosts();
        tagProvidersAndHosts();


@@ -3012,6 +3052,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
        }
    }
    }


    @GuardedBy("mLock")
    private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
    private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
        int N;
        int N;


@@ -3028,7 +3069,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                if (provider.getUserId() != userId) {
                if (provider.getUserId() != userId) {
                    continue;
                    continue;
                }
                }
                if (provider.shouldBePersisted()) {
                if (mIsProviderInfoPersisted) {
                    serializeProviderWithProviderInfo(out, provider);
                } else if (provider.shouldBePersisted()) {
                    serializeProvider(out, provider);
                    serializeProvider(out, provider);
                }
                }
            }
            }
@@ -3074,6 +3117,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        }
        }
    }
    }


    @GuardedBy("mLock")
    private int readProfileStateFromFileLocked(FileInputStream stream, int userId,
    private int readProfileStateFromFileLocked(FileInputStream stream, int userId,
            List<LoadedWidgetState> outLoadedWidgets) {
            List<LoadedWidgetState> outLoadedWidgets) {
        int version = -1;
        int version = -1;
@@ -3127,6 +3171,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                            provider.zombie = true;
                            provider.zombie = true;
                            provider.id = providerId;
                            provider.id = providerId;
                            mProviders.add(provider);
                            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",
                        final int providerTag = parser.getAttributeIntHex(null, "tag",
+131 −0
Original line number Original line Diff line number Diff line
/*
 * 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;
    }
}
+92 −0
Original line number Original line Diff line number Diff line
@@ -28,6 +28,9 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
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.app.admin.DevicePolicyManagerInternal;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetManagerInternal;
import android.appwidget.AppWidgetManagerInternal;
@@ -39,11 +42,16 @@ import android.content.ContextWrapper;
import android.content.Intent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal;
import android.os.Handler;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
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 android.widget.RemoteViews;


import com.android.frameworks.servicestests.R;
import com.android.frameworks.servicestests.R;
@@ -51,9 +59,16 @@ import com.android.internal.appwidget.IAppWidgetHost;
import com.android.server.LocalServices;
import com.android.server.LocalServices;


import org.mockito.ArgumentCaptor;
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.Iterator;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CountDownLatch;


@@ -77,6 +92,8 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
    private AppWidgetManager mManager;
    private AppWidgetManager mManager;


    private ShortcutServiceInternal mMockShortcutService;
    private ShortcutServiceInternal mMockShortcutService;
    private PackageManagerInternal mMockPackageManager;
    private AppOpsManagerInternal mMockAppOpsManagerInternal;
    private IAppWidgetHost mMockHost;
    private IAppWidgetHost mMockHost;


    @Override
    @Override
@@ -85,6 +102,8 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
        LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
        LocalServices.removeServiceForTest(AppWidgetManagerInternal.class);
        LocalServices.removeServiceForTest(AppWidgetManagerInternal.class);
        LocalServices.removeServiceForTest(PackageManagerInternal.class);
        LocalServices.removeServiceForTest(AppOpsManagerInternal.class);


        mTestContext = new TestContext();
        mTestContext = new TestContext();
        mPkgName = mTestContext.getOpPackageName();
        mPkgName = mTestContext.getOpPackageName();
@@ -92,9 +111,16 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
        mManager = new AppWidgetManager(mTestContext, mService);
        mManager = new AppWidgetManager(mTestContext, mService);


        mMockShortcutService = mock(ShortcutServiceInternal.class);
        mMockShortcutService = mock(ShortcutServiceInternal.class);
        mMockPackageManager = mock(PackageManagerInternal.class);
        mMockAppOpsManagerInternal = mock(AppOpsManagerInternal.class);
        mMockHost = mock(IAppWidgetHost.class);
        mMockHost = mock(IAppWidgetHost.class);
        LocalServices.addService(ShortcutServiceInternal.class, mMockShortcutService);
        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.onStart();
        mService.systemServicesReady();
    }
    }


    public void testLoadDescription() {
    public void testLoadDescription() {
@@ -323,6 +349,34 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
        assertThat(info.previewLayout).isEqualTo(R.layout.widget_preview);
        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() {
    private int setupHostAndWidget() {
        List<PendingHostUpdate> updates = mService.startListening(
        List<PendingHostUpdate> updates = mService.startListening(
                mMockHost, mPkgName, HOST_ID, new int[0]).getList();
                mMockHost, mPkgName, HOST_ID, new int[0]).getList();
@@ -353,6 +407,44 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase {
        return mTestContext.getResources().getInteger(resId);
        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 {
    private class TestContext extends ContextWrapper {


        public TestContext() {
        public TestContext() {