RegisteredServicesCache.java revision 3ecd5f437580e49d80beecd29489d5fb1f7a7db0
1/* 2 * Copyright (C) 2009 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 17package android.content.pm; 18 19import android.content.Context; 20import android.content.BroadcastReceiver; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.content.ComponentName; 24import android.content.res.XmlResourceParser; 25import android.util.Log; 26import android.util.AttributeSet; 27import android.util.Xml; 28 29import java.util.Map; 30import java.util.Collection; 31import java.util.Collections; 32import java.util.List; 33import java.io.FileDescriptor; 34import java.io.PrintWriter; 35import java.io.IOException; 36 37import com.google.android.collect.Maps; 38import org.xmlpull.v1.XmlPullParserException; 39import org.xmlpull.v1.XmlPullParser; 40 41/** 42 * A cache of registered services. This cache 43 * is built by interrogating the {@link PackageManager} and is updated as packages are added, 44 * removed and changed. The services are referred to by type V and 45 * are made available via the {@link #getServiceInfo} method. 46 * @hide 47 */ 48public abstract class RegisteredServicesCache<V> { 49 private static final String TAG = "PackageManager"; 50 51 public final Context mContext; 52 private final String mInterfaceName; 53 private final String mMetaDataName; 54 private final String mAttributesName; 55 56 public RegisteredServicesCacheListener getListener() { 57 return mListener; 58 } 59 60 public void setListener(RegisteredServicesCacheListener listener) { 61 mListener = listener; 62 } 63 64 private volatile RegisteredServicesCacheListener mListener; 65 66 // no need to be synchronized since the map is never changed once mService is written 67 volatile Map<V, ServiceInfo<V>> mServices; 68 69 // synchronized on "this" 70 private BroadcastReceiver mReceiver = null; 71 72 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName, 73 String attributeName) { 74 mContext = context; 75 mInterfaceName = interfaceName; 76 mMetaDataName = metaDataName; 77 mAttributesName = attributeName; 78 } 79 80 public void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 81 getAllServices(); 82 Map<V, ServiceInfo<V>> services = mServices; 83 fout.println("RegisteredServicesCache: " + services.size() + " services"); 84 for (ServiceInfo info : services.values()) { 85 fout.println(" " + info); 86 } 87 } 88 89 private boolean maybeRegisterForPackageChanges() { 90 synchronized (this) { 91 if (mReceiver == null) { 92 synchronized (this) { 93 mReceiver = new BroadcastReceiver() { 94 @Override 95 public void onReceive(Context context, Intent intent) { 96 mServices = generateServicesMap(); 97 RegisteredServicesCacheListener listener = mListener; 98 if (listener != null) { 99 listener.onRegisteredServicesCacheChanged(); 100 } 101 } 102 }; 103 } 104 105 IntentFilter intentFilter = new IntentFilter(); 106 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 107 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 108 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 109 intentFilter.addDataScheme("package"); 110 mContext.registerReceiver(mReceiver, intentFilter); 111 return true; 112 } 113 return false; 114 } 115 } 116 117 private void maybeUnregisterForPackageChanges() { 118 synchronized (this) { 119 if (mReceiver != null) { 120 mContext.unregisterReceiver(mReceiver); 121 mReceiver = null; 122 } 123 } 124 } 125 126 /** 127 * Value type that describes a Service. The information within can be used 128 * to bind to the service. 129 */ 130 public static class ServiceInfo<V> { 131 public final V type; 132 public final ComponentName componentName; 133 public final int uid; 134 135 private ServiceInfo(V type, ComponentName componentName, int uid) { 136 this.type = type; 137 this.componentName = componentName; 138 this.uid = uid; 139 } 140 141 @Override 142 public String toString() { 143 return "ServiceInfo: " + type + ", " + componentName; 144 } 145 } 146 147 /** 148 * Accessor for the registered authenticators. 149 * @param type the account type of the authenticator 150 * @return the AuthenticatorInfo that matches the account type or null if none is present 151 */ 152 public ServiceInfo<V> getServiceInfo(V type) { 153 if (mServices == null) { 154 maybeRegisterForPackageChanges(); 155 mServices = generateServicesMap(); 156 } 157 return mServices.get(type); 158 } 159 160 /** 161 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all 162 * registered authenticators. 163 */ 164 public Collection<ServiceInfo<V>> getAllServices() { 165 if (mServices == null) { 166 maybeRegisterForPackageChanges(); 167 mServices = generateServicesMap(); 168 } 169 return Collections.unmodifiableCollection(mServices.values()); 170 } 171 172 /** 173 * Stops the monitoring of package additions, removals and changes. 174 */ 175 public void close() { 176 maybeUnregisterForPackageChanges(); 177 } 178 179 @Override 180 protected void finalize() throws Throwable { 181 synchronized (this) { 182 if (mReceiver != null) { 183 Log.e(TAG, "RegisteredServicesCache finalized without being closed"); 184 } 185 } 186 close(); 187 super.finalize(); 188 } 189 190 Map<V, ServiceInfo<V>> generateServicesMap() { 191 Map<V, ServiceInfo<V>> services = Maps.newHashMap(); 192 PackageManager pm = mContext.getPackageManager(); 193 194 List<ResolveInfo> resolveInfos = 195 pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA); 196 197 for (ResolveInfo resolveInfo : resolveInfos) { 198 try { 199 ServiceInfo<V> info = parseServiceInfo(resolveInfo); 200 if (info != null) { 201 services.put(info.type, info); 202 } else { 203 Log.w(TAG, "Unable to load input method " + resolveInfo.toString()); 204 } 205 } catch (XmlPullParserException e) { 206 Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); 207 } catch (IOException e) { 208 Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); 209 } 210 } 211 212 return services; 213 } 214 215 private ServiceInfo<V> parseServiceInfo(ResolveInfo service) 216 throws XmlPullParserException, IOException { 217 android.content.pm.ServiceInfo si = service.serviceInfo; 218 ComponentName componentName = new ComponentName(si.packageName, si.name); 219 220 PackageManager pm = mContext.getPackageManager(); 221 222 XmlResourceParser parser = null; 223 try { 224 parser = si.loadXmlMetaData(pm, mMetaDataName); 225 if (parser == null) { 226 throw new XmlPullParserException("No " + mMetaDataName + " meta-data"); 227 } 228 229 AttributeSet attrs = Xml.asAttributeSet(parser); 230 231 int type; 232 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 233 && type != XmlPullParser.START_TAG) { 234 } 235 236 String nodeName = parser.getName(); 237 if (!mAttributesName.equals(nodeName)) { 238 throw new XmlPullParserException( 239 "Meta-data does not start with " + mAttributesName + " tag"); 240 } 241 242 V v = parseServiceAttributes(si.packageName, attrs); 243 if (v == null) { 244 return null; 245 } 246 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo; 247 final ApplicationInfo applicationInfo = serviceInfo.applicationInfo; 248 final int uid = applicationInfo.uid; 249 return new ServiceInfo<V>(v, componentName, uid); 250 } finally { 251 if (parser != null) parser.close(); 252 } 253 } 254 255 public abstract V parseServiceAttributes(String packageName, AttributeSet attrs); 256} 257