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

Commit 24a37075 authored by Presubmit Automerger Backend's avatar Presubmit Automerger Backend
Browse files

[automerge] Implement extra persistence layer for widget provider info 2p: 09ddda9f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17355277

Bug: 202356231
Change-Id: I768137d33a9903d200c25bc8ac2754ab5813eee2
parents b2d7952f 09ddda9f
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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() {
    }
}
+58 −2
Original line number 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_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",
+131 −0
Original line number 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 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.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() {