Merge "Implement extra persistence layer for widget provider info" into tm-dev
This commit is contained in:
@@ -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() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.appwidget;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.appwidget.AppWidgetProviderInfo;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.TypedXmlPullParser;
|
||||||
|
import android.util.TypedXmlSerializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class AppWidgetXmlUtil {
|
||||||
|
|
||||||
|
private static final String ATTR_MIN_WIDTH = "min_width";
|
||||||
|
private static final String ATTR_MIN_HEIGHT = "min_height";
|
||||||
|
private static final String ATTR_MIN_RESIZE_WIDTH = "min_resize_width";
|
||||||
|
private static final String ATTR_MIN_RESIZE_HEIGHT = "min_resize_height";
|
||||||
|
private static final String ATTR_MAX_RESIZE_WIDTH = "max_resize_width";
|
||||||
|
private static final String ATTR_MAX_RESIZE_HEIGHT = "max_resize_height";
|
||||||
|
private static final String ATTR_TARGET_CELL_WIDTH = "target_cell_width";
|
||||||
|
private static final String ATTR_TARGET_CELL_HEIGHT = "target_cell_height";
|
||||||
|
private static final String ATTR_UPDATE_PERIOD_MILLIS = "update_period_millis";
|
||||||
|
private static final String ATTR_INITIAL_LAYOUT = "initial_layout";
|
||||||
|
private static final String ATTR_INITIAL_KEYGUARD_LAYOUT = "initial_keyguard_layout";
|
||||||
|
private static final String ATTR_CONFIGURE = "configure";
|
||||||
|
private static final String ATTR_LABEL = "label";
|
||||||
|
private static final String ATTR_ICON = "icon";
|
||||||
|
private static final String ATTR_PREVIEW_IMAGE = "preview_image";
|
||||||
|
private static final String ATTR_PREVIEW_LAYOUT = "preview_layout";
|
||||||
|
private static final String ATTR_AUTO_ADVANCED_VIEW_ID = "auto_advance_view_id";
|
||||||
|
private static final String ATTR_RESIZE_MODE = "resize_mode";
|
||||||
|
private static final String ATTR_WIDGET_CATEGORY = "widget_category";
|
||||||
|
private static final String ATTR_WIDGET_FEATURES = "widget_features";
|
||||||
|
private static final String ATTR_DESCRIPTION_RES = "description_res";
|
||||||
|
private static final String ATTR_OS_FINGERPRINT = "os_fingerprint";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static void writeAppWidgetProviderInfoLocked(@NonNull final TypedXmlSerializer out,
|
||||||
|
@NonNull final AppWidgetProviderInfo info) throws IOException {
|
||||||
|
Objects.requireNonNull(out);
|
||||||
|
Objects.requireNonNull(info);
|
||||||
|
out.attributeInt(null, ATTR_MIN_WIDTH, info.minWidth);
|
||||||
|
out.attributeInt(null, ATTR_MIN_HEIGHT, info.minHeight);
|
||||||
|
out.attributeInt(null, ATTR_MIN_RESIZE_WIDTH, info.minResizeWidth);
|
||||||
|
out.attributeInt(null, ATTR_MIN_RESIZE_HEIGHT, info.minResizeHeight);
|
||||||
|
out.attributeInt(null, ATTR_MAX_RESIZE_WIDTH, info.maxResizeWidth);
|
||||||
|
out.attributeInt(null, ATTR_MAX_RESIZE_HEIGHT, info.maxResizeHeight);
|
||||||
|
out.attributeInt(null, ATTR_TARGET_CELL_WIDTH, info.targetCellWidth);
|
||||||
|
out.attributeInt(null, ATTR_TARGET_CELL_HEIGHT, info.targetCellHeight);
|
||||||
|
out.attributeInt(null, ATTR_UPDATE_PERIOD_MILLIS, info.updatePeriodMillis);
|
||||||
|
out.attributeInt(null, ATTR_INITIAL_LAYOUT, info.initialLayout);
|
||||||
|
out.attributeInt(null, ATTR_INITIAL_KEYGUARD_LAYOUT, info.initialKeyguardLayout);
|
||||||
|
if (info.configure != null) {
|
||||||
|
out.attribute(null, ATTR_CONFIGURE, info.configure.flattenToShortString());
|
||||||
|
}
|
||||||
|
out.attribute(null, ATTR_LABEL, info.label);
|
||||||
|
out.attributeInt(null, ATTR_ICON, info.icon);
|
||||||
|
out.attributeInt(null, ATTR_PREVIEW_IMAGE, info.previewImage);
|
||||||
|
out.attributeInt(null, ATTR_PREVIEW_LAYOUT, info.previewLayout);
|
||||||
|
out.attributeInt(null, ATTR_AUTO_ADVANCED_VIEW_ID, info.autoAdvanceViewId);
|
||||||
|
out.attributeInt(null, ATTR_RESIZE_MODE, info.resizeMode);
|
||||||
|
out.attributeInt(null, ATTR_WIDGET_CATEGORY, info.widgetCategory);
|
||||||
|
out.attributeInt(null, ATTR_WIDGET_FEATURES, info.widgetFeatures);
|
||||||
|
out.attributeInt(null, ATTR_DESCRIPTION_RES, info.descriptionRes);
|
||||||
|
out.attribute(null, ATTR_OS_FINGERPRINT, Build.FINGERPRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static AppWidgetProviderInfo readAppWidgetProviderInfoLocked(
|
||||||
|
@NonNull final TypedXmlPullParser parser) {
|
||||||
|
Objects.requireNonNull(parser);
|
||||||
|
final String fingerprint = parser.getAttributeValue(null, ATTR_OS_FINGERPRINT);
|
||||||
|
if (!Build.FINGERPRINT.equals(fingerprint)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final AppWidgetProviderInfo info = new AppWidgetProviderInfo();
|
||||||
|
info.minWidth = parser.getAttributeInt(null, ATTR_MIN_WIDTH, 0);
|
||||||
|
info.minHeight = parser.getAttributeInt(null, ATTR_MIN_HEIGHT, 0);
|
||||||
|
info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_WIDTH, 0);
|
||||||
|
info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
|
||||||
|
info.maxResizeWidth = parser.getAttributeInt(null, ATTR_MAX_RESIZE_WIDTH, 0);
|
||||||
|
info.maxResizeHeight = parser.getAttributeInt(null, ATTR_MAX_RESIZE_HEIGHT, 0);
|
||||||
|
info.targetCellWidth = parser.getAttributeInt(null, ATTR_TARGET_CELL_WIDTH, 0);
|
||||||
|
info.targetCellHeight = parser.getAttributeInt(null, ATTR_TARGET_CELL_HEIGHT, 0);
|
||||||
|
info.updatePeriodMillis = parser.getAttributeInt(null, ATTR_UPDATE_PERIOD_MILLIS, 0);
|
||||||
|
info.initialLayout = parser.getAttributeInt(null, ATTR_INITIAL_LAYOUT, 0);
|
||||||
|
info.initialKeyguardLayout = parser.getAttributeInt(
|
||||||
|
null, ATTR_INITIAL_KEYGUARD_LAYOUT, 0);
|
||||||
|
final String configure = parser.getAttributeValue(null, ATTR_CONFIGURE);
|
||||||
|
if (!TextUtils.isEmpty(configure)) {
|
||||||
|
info.configure = ComponentName.unflattenFromString(configure);
|
||||||
|
}
|
||||||
|
info.label = parser.getAttributeValue(null, ATTR_LABEL);
|
||||||
|
info.icon = parser.getAttributeInt(null, ATTR_ICON, 0);
|
||||||
|
info.previewImage = parser.getAttributeInt(null, ATTR_PREVIEW_IMAGE, 0);
|
||||||
|
info.previewLayout = parser.getAttributeInt(null, ATTR_PREVIEW_LAYOUT, 0);
|
||||||
|
info.autoAdvanceViewId = parser.getAttributeInt(null, ATTR_AUTO_ADVANCED_VIEW_ID, 0);
|
||||||
|
info.resizeMode = parser.getAttributeInt(null, ATTR_RESIZE_MODE, 0);
|
||||||
|
info.widgetCategory = parser.getAttributeInt(null, ATTR_WIDGET_CATEGORY, 0);
|
||||||
|
info.widgetFeatures = parser.getAttributeInt(null, ATTR_WIDGET_FEATURES, 0);
|
||||||
|
info.descriptionRes = parser.getAttributeInt(null, ATTR_DESCRIPTION_RES, 0);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user