/* * Copyright (C) 2007 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.internal.policy; import android.annotation.RequiresPermission; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.net.Uri; import android.os.Handler; import android.os.UserHandle; import android.util.ArrayMap; import android.util.LruCache; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; /** * TODO: This should be better integrated into the system so it doesn't need * special calls from the activity manager to clear it. */ /** @hide */ public final class AttributeCache { private static final int CACHE_SIZE = 4; private static AttributeCache sInstance = null; private final Context mContext; @GuardedBy("this") private final LruCache mPackages = new LruCache<>(CACHE_SIZE); @GuardedBy("this") private final Configuration mConfiguration = new Configuration(); private PackageMonitor mPackageMonitor; public final static class Package { public final Context context; private final SparseArray> mMap = new SparseArray<>(); public Package(Context c) { context = c; } } public final static class Entry { public final Context context; public final TypedArray array; public Entry(Context c, TypedArray ta) { context = c; array = ta; } void recycle() { if (array != null) { array.recycle(); } } } public static void init(Context context) { if (sInstance == null) { sInstance = new AttributeCache(context); } } /** * Start monitor package change, so the resources can be loaded correctly. */ void monitorPackageRemove(Handler handler) { if (mPackageMonitor == null) { mPackageMonitor = new PackageMonitor(mContext, handler); } } static class PackageMonitor extends BroadcastReceiver { @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) PackageMonitor(Context context, Handler handler) { final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme(IntentFilter.SCHEME_PACKAGE); context.registerReceiverAsUser(this, UserHandle.ALL, filter, null /* broadcastPermission */, handler); } @Override public void onReceive(Context context, Intent intent) { final Uri packageUri = intent.getData(); if (packageUri != null) { final String packageName = packageUri.getEncodedSchemeSpecificPart(); AttributeCache.instance().removePackage(packageName); } } } public static AttributeCache instance() { return sInstance; } public AttributeCache(Context context) { mContext = context; } public void removePackage(String packageName) { synchronized (this) { final Package pkg = mPackages.remove(packageName); if (pkg != 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); if ((changes & ~(ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_KEYBOARD_HIDDEN | ActivityInfo.CONFIG_ORIENTATION)) != 0) { // The configurations being masked out are ones that commonly // change so we don't want flushing the cache... all others // will flush the cache. mPackages.evictAll(); } } } public Entry get(String packageName, int resId, int[] styleable) { return get(packageName, resId, styleable, UserHandle.USER_CURRENT); } public Entry get(String packageName, int resId, int[] styleable, int userId) { synchronized (this) { Package pkg = mPackages.get(packageName); ArrayMap map = null; Entry ent = null; if (pkg != null) { map = pkg.mMap.get(resId); if (map != null) { ent = map.get(styleable); if (ent != null) { return ent; } } } else { Context context; try { context = mContext.createPackageContextAsUser(packageName, 0, new UserHandle(userId)); if (context == null) { return null; } } catch (PackageManager.NameNotFoundException e) { return null; } pkg = new Package(context); mPackages.put(packageName, pkg); } if (map == null) { map = new ArrayMap<>(); pkg.mMap.put(resId, map); } try { ent = new Entry(pkg.context, pkg.context.obtainStyledAttributes(resId, styleable)); map.put(styleable, ent); } catch (Resources.NotFoundException e) { return null; } return ent; } } }