diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 25a8b66a42aad..31d254dc5b2bf 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -29,7 +29,6 @@ import android.content.res.ResourcesImpl; import android.content.res.ResourcesKey; import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; -import android.os.LocaleList; import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -38,13 +37,12 @@ import android.util.Pair; import android.util.Slog; import android.view.Display; import android.view.DisplayAdjustments; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -120,6 +118,30 @@ public class ResourcesManager { } } + /** + * Invalidate and destroy any resources that reference content under the + * given filesystem path. Typically used when unmounting a storage device to + * try as hard as possible to release any open FDs. + */ + public void invalidatePath(String path) { + synchronized (this) { + int count = 0; + for (int i = 0; i < mResourceImpls.size();) { + final ResourcesKey key = mResourceImpls.keyAt(i); + if (key.isPathReferenced(path)) { + final ResourcesImpl res = mResourceImpls.removeAt(i).get(); + if (res != null) { + res.flushLayoutCache(); + } + count++; + } else { + i++; + } + } + Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path); + } + } + public Configuration getConfiguration() { synchronized (this) { return mResConfiguration; diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java index e89449283c5e2..64b6bf1b6dcd3 100644 --- a/core/java/android/content/res/ResourcesKey.java +++ b/core/java/android/content/res/ResourcesKey.java @@ -77,6 +77,26 @@ public final class ResourcesKey { return !Configuration.EMPTY.equals(mOverrideConfiguration); } + public boolean isPathReferenced(String path) { + if (mResDir != null && mResDir.startsWith(path)) { + return true; + } else { + return anyStartsWith(mSplitResDirs, path) || anyStartsWith(mOverlayDirs, path) + || anyStartsWith(mLibDirs, path); + } + } + + private static boolean anyStartsWith(String[] list, String prefix) { + if (list != null) { + for (String s : list) { + if (s != null && s.startsWith(prefix)) { + return true; + } + } + } + return false; + } + @Override public int hashCode() { return mHash; diff --git a/services/core/java/com/android/server/AttributeCache.java b/services/core/java/com/android/server/AttributeCache.java index 427dbc091ced5..57f18c086c56c 100644 --- a/services/core/java/com/android/server/AttributeCache.java +++ b/services/core/java/com/android/server/AttributeCache.java @@ -24,10 +24,12 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.SparseArray; -import java.util.HashMap; -import java.util.WeakHashMap; +import com.android.internal.annotations.GuardedBy; + +import java.lang.ref.WeakReference; /** * TODO: This should be better integrated into the system so it doesn't need @@ -35,17 +37,18 @@ import java.util.WeakHashMap; */ public final class AttributeCache { private static AttributeCache sInstance = null; - + private final Context mContext; - private final WeakHashMap mPackages = - new WeakHashMap(); + + @GuardedBy("this") + private final ArrayMap> mPackages = new ArrayMap<>(); + @GuardedBy("this") private final Configuration mConfiguration = new Configuration(); - + public final static class Package { public final Context context; - private final SparseArray> mMap - = new SparseArray>(); - + private final SparseArray> mMap = new SparseArray<>(); + public Package(Context c) { context = c; } @@ -59,6 +62,12 @@ public final class AttributeCache { context = c; array = ta; } + + void recycle() { + if (array != null) { + array.recycle(); + } + } } public static void init(Context context) { @@ -74,13 +83,27 @@ public final class AttributeCache { public AttributeCache(Context context) { mContext = context; } - + public void removePackage(String packageName) { synchronized (this) { - mPackages.remove(packageName); + final WeakReference ref = mPackages.remove(packageName); + final Package pkg = (ref != null) ? ref.get() : null; + if (pkg != null) { + if (pkg.mMap != null) { + for (int i = 0; i < pkg.mMap.size(); i++) { + final ArrayMap map = pkg.mMap.valueAt(i); + for (int j = 0; j < map.size(); j++) { + map.valueAt(j).recycle(); + } + } + } + + final Resources res = pkg.context.getResources(); + res.flushLayoutCache(); + } } } - + public void updateConfiguration(Configuration config) { synchronized (this) { int changes = mConfiguration.updateFrom(config); @@ -97,8 +120,9 @@ public final class AttributeCache { public Entry get(String packageName, int resId, int[] styleable, int userId) { synchronized (this) { - Package pkg = mPackages.get(packageName); - HashMap map = null; + WeakReference ref = mPackages.get(packageName); + Package pkg = (ref != null) ? ref.get() : null; + ArrayMap map = null; Entry ent = null; if (pkg != null) { map = pkg.mMap.get(resId); @@ -120,11 +144,11 @@ public final class AttributeCache { return null; } pkg = new Package(context); - mPackages.put(packageName, pkg); + mPackages.put(packageName, new WeakReference<>(pkg)); } if (map == null) { - map = new HashMap(); + map = new ArrayMap<>(); pkg.mMap.put(resId, map); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0fed1942add4a..26d4e41fe5132 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -105,6 +105,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.IActivityManager; +import android.app.ResourcesManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; import android.app.admin.SecurityLog; @@ -238,6 +239,7 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.AttributeCache; import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.IntentResolver; @@ -19142,6 +19144,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); Slog.w(TAG, "Failed to unload " + ps.codePath); } } + + // Try very hard to release any references to this package + // so we don't risk the system server being killed due to + // open FDs + AttributeCache.instance().removePackage(ps.name); } mSettings.writeLPr(); @@ -19150,6 +19157,15 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded); sendResourcesChangedBroadcast(false, false, unloaded, null); + + // Try very hard to release any references to this path so we don't risk + // the system server being killed due to open FDs + ResourcesManager.getInstance().invalidatePath(vol.getPath().getAbsolutePath()); + + for (int i = 0; i < 3; i++) { + System.gc(); + System.runFinalization(); + } } /**