Loading core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +6 −0 Original line number Original line Diff line number Diff line Loading @@ -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() { } } } } services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +58 −2 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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( Loading Loading @@ -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); Loading Loading @@ -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()); Loading @@ -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"); } } Loading Loading @@ -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 Loading Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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); } } } } Loading Loading @@ -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; Loading Loading @@ -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", Loading services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java 0 → 100644 +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; } } services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +92 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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(); Loading @@ -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() { Loading Loading @@ -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(); Loading Loading @@ -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() { Loading Loading
core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +6 −0 Original line number Original line Diff line number Diff line Loading @@ -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() { } } } }
services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +58 −2 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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( Loading Loading @@ -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); Loading Loading @@ -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()); Loading @@ -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"); } } Loading Loading @@ -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 Loading Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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); } } } } Loading Loading @@ -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; Loading Loading @@ -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", Loading
services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java 0 → 100644 +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; } }
services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +92 −0 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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(); Loading @@ -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() { Loading Loading @@ -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(); Loading Loading @@ -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() { Loading