1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.policy;
18 
19 import android.annotation.RequiresPermission;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.PackageManager;
26 import android.content.res.Configuration;
27 import android.content.res.Resources;
28 import android.content.res.TypedArray;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.UserHandle;
32 import android.util.ArrayMap;
33 import android.util.LruCache;
34 import android.util.SparseArray;
35 
36 import com.android.internal.annotations.GuardedBy;
37 
38 /**
39  * TODO: This should be better integrated into the system so it doesn't need
40  * special calls from the activity manager to clear it.
41  */
42 /** @hide */
43 public final class AttributeCache {
44     private static final int CACHE_SIZE = 4;
45     private static AttributeCache sInstance = null;
46 
47     private final Context mContext;
48 
49     @GuardedBy("this")
50     private final LruCache<String, Package> mPackages = new LruCache<>(CACHE_SIZE);
51 
52     @GuardedBy("this")
53     private final Configuration mConfiguration = new Configuration();
54 
55     private PackageMonitor mPackageMonitor;
56 
57     public final static class Package {
58         public final Context context;
59         private final SparseArray<ArrayMap<int[], Entry>> mMap = new SparseArray<>();
60 
Package(Context c)61         public Package(Context c) {
62             context = c;
63         }
64     }
65 
66     public final static class Entry {
67         public final Context context;
68         public final TypedArray array;
69 
Entry(Context c, TypedArray ta)70         public Entry(Context c, TypedArray ta) {
71             context = c;
72             array = ta;
73         }
74 
recycle()75         void recycle() {
76             if (array != null) {
77                 array.recycle();
78             }
79         }
80     }
81 
init(Context context)82     public static void init(Context context) {
83         if (sInstance == null) {
84             sInstance = new AttributeCache(context);
85         }
86     }
87 
88     /**
89      * Start monitor package change, so the resources can be loaded correctly.
90      */
monitorPackageRemove(Handler handler)91     void monitorPackageRemove(Handler handler) {
92         if (mPackageMonitor == null) {
93             mPackageMonitor = new PackageMonitor(mContext, handler);
94         }
95     }
96 
97     static class PackageMonitor extends BroadcastReceiver {
98         @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
PackageMonitor(Context context, Handler handler)99         PackageMonitor(Context context, Handler handler) {
100             final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
101             filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
102             context.registerReceiverAsUser(this, UserHandle.ALL, filter,
103                     null /* broadcastPermission */, handler);
104         }
105 
106         @Override
onReceive(Context context, Intent intent)107         public void onReceive(Context context, Intent intent) {
108             final Uri packageUri = intent.getData();
109             if (packageUri != null) {
110                 final String packageName = packageUri.getEncodedSchemeSpecificPart();
111                 AttributeCache.instance().removePackage(packageName);
112             }
113         }
114     }
115 
instance()116     public static AttributeCache instance() {
117         return sInstance;
118     }
119 
AttributeCache(Context context)120     public AttributeCache(Context context) {
121         mContext = context;
122     }
123 
removePackage(String packageName)124     public void removePackage(String packageName) {
125         synchronized (this) {
126             final Package pkg = mPackages.remove(packageName);
127             if (pkg != null) {
128                 for (int i = 0; i < pkg.mMap.size(); i++) {
129                     final ArrayMap<int[], Entry> map = pkg.mMap.valueAt(i);
130                     for (int j = 0; j < map.size(); j++) {
131                         map.valueAt(j).recycle();
132                     }
133                 }
134 
135                 final Resources res = pkg.context.getResources();
136                 res.flushLayoutCache();
137             }
138         }
139     }
140 
updateConfiguration(Configuration config)141     public void updateConfiguration(Configuration config) {
142         synchronized (this) {
143             int changes = mConfiguration.updateFrom(config);
144             if ((changes & ~(ActivityInfo.CONFIG_FONT_SCALE |
145                     ActivityInfo.CONFIG_KEYBOARD_HIDDEN |
146                     ActivityInfo.CONFIG_ORIENTATION)) != 0) {
147                 // The configurations being masked out are ones that commonly
148                 // change so we don't want flushing the cache... all others
149                 // will flush the cache.
150                 mPackages.evictAll();
151             }
152         }
153     }
154 
get(String packageName, int resId, int[] styleable)155     public Entry get(String packageName, int resId, int[] styleable) {
156         return get(packageName, resId, styleable, UserHandle.USER_CURRENT);
157     }
158 
get(String packageName, int resId, int[] styleable, int userId)159     public Entry get(String packageName, int resId, int[] styleable, int userId) {
160         synchronized (this) {
161             Package pkg = mPackages.get(packageName);
162             ArrayMap<int[], Entry> map = null;
163             Entry ent = null;
164             if (pkg != null) {
165                 map = pkg.mMap.get(resId);
166                 if (map != null) {
167                     ent = map.get(styleable);
168                     if (ent != null) {
169                         return ent;
170                     }
171                 }
172             } else {
173                 Context context;
174                 try {
175                     context = mContext.createPackageContextAsUser(packageName, 0,
176                             new UserHandle(userId));
177                     if (context == null) {
178                         return null;
179                     }
180                 } catch (PackageManager.NameNotFoundException e) {
181                     return null;
182                 }
183                 pkg = new Package(context);
184                 mPackages.put(packageName, pkg);
185             }
186 
187             if (map == null) {
188                 map = new ArrayMap<>();
189                 pkg.mMap.put(resId, map);
190             }
191 
192             try {
193                 ent = new Entry(pkg.context,
194                         pkg.context.obtainStyledAttributes(resId, styleable));
195                 map.put(styleable, ent);
196             } catch (Resources.NotFoundException e) {
197                 return null;
198             }
199 
200             return ent;
201         }
202     }
203 }
204 
205