Release AssetManagers when ejecting storage.

When ejecting a storage device, the system process needs to rapidly
release any open FDs to prevent itself from being killed by vold.

This change examines all ResourceImpls cached inside the system
process and evicts any that reference the storage device being
ejected.  (ResourcesManager will gladly recreate any evicted entries
when asked again in the future.)

Also replace broken use of WeakHashMap, since we want the values to
be weak references, not the keys.

Bug: 28867548
Change-Id: Ib9cfc66497149b6d3f8d49213e9779408a331d2a
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 25a8b66..31d254dc 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -29,7 +29,6 @@
 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.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 @@
         }
     }
 
+    /**
+     * 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 e894492..64b6bf1 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -77,6 +77,26 @@
         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 427dbc0..57f18c0 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.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 @@
  */
 public final class AttributeCache {
     private static AttributeCache sInstance = null;
-    
+
     private final Context mContext;
-    private final WeakHashMap<String, Package> mPackages =
-            new WeakHashMap<String, Package>();
+
+    @GuardedBy("this")
+    private final ArrayMap<String, WeakReference<Package>> mPackages = new ArrayMap<>();
+    @GuardedBy("this")
     private final Configuration mConfiguration = new Configuration();
-    
+
     public final static class Package {
         public final Context context;
-        private final SparseArray<HashMap<int[], Entry>> mMap
-                = new SparseArray<HashMap<int[], Entry>>();
-        
+        private final SparseArray<ArrayMap<int[], Entry>> mMap = new SparseArray<>();
+
         public Package(Context c) {
             context = c;
         }
@@ -59,6 +62,12 @@
             context = c;
             array = ta;
         }
+
+        void recycle() {
+            if (array != null) {
+                array.recycle();
+            }
+        }
     }
     
     public static void init(Context context) {
@@ -74,13 +83,27 @@
     public AttributeCache(Context context) {
         mContext = context;
     }
-    
+
     public void removePackage(String packageName) {
         synchronized (this) {
-            mPackages.remove(packageName);
+            final WeakReference<Package> 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<int[], Entry> 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 Entry get(String packageName, int resId, int[] styleable, int userId) {
         synchronized (this) {
-            Package pkg = mPackages.get(packageName);
-            HashMap<int[], Entry> map = null;
+            WeakReference<Package> ref = mPackages.get(packageName);
+            Package pkg = (ref != null) ? ref.get() : null;
+            ArrayMap<int[], Entry> map = null;
             Entry ent = null;
             if (pkg != null) {
                 map = pkg.mMap.get(resId);
@@ -120,11 +144,11 @@
                     return null;
                 }
                 pkg = new Package(context);
-                mPackages.put(packageName, pkg);
+                mPackages.put(packageName, new WeakReference<>(pkg));
             }
             
             if (map == null) {
-                map = new HashMap<int[], Entry>();
+                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 a2bdde4..3450aff 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.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.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;
@@ -19135,6 +19137,11 @@
                         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();
@@ -19143,6 +19150,15 @@
 
         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();
+        }
     }
 
     /**