RegisteredServicesCache.java revision d4a1d2e14297a3387fdb5761090961e714370492
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 // no need to be synchronized since the map is never changed once mService is written 57 private volatile Map<V, ServiceInfo<V>> mServices; 58 59 // synchronized on "this" 60 private BroadcastReceiver mReceiver = null; 61 62 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName, 63 String attributeName) { 64 mContext = context; 65 mInterfaceName = interfaceName; 66 mMetaDataName = metaDataName; 67 mAttributesName = attributeName; 68 } 69 70 public void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 71 getAllServices(); 72 Map<V, ServiceInfo<V>> services = mServices; 73 fout.println("RegisteredServicesCache: " + services.size() + " services"); 74 for (ServiceInfo info : services.values()) { 75 fout.println(" " + info); 76 } 77 } 78 79 private boolean maybeRegisterForPackageChanges() { 80 synchronized (this) { 81 if (mReceiver == null) { 82 synchronized (this) { 83 mReceiver = new BroadcastReceiver() { 84 public void onReceive(Context context, Intent intent) { 85 mServices = generateServicesMap(); 86 } 87 }; 88 } 89 90 IntentFilter intentFilter = new IntentFilter(); 91 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 92 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 93 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 94 mContext.registerReceiver(mReceiver, intentFilter); 95 return true; 96 } 97 return false; 98 } 99 } 100 101 private void maybeUnregisterForPackageChanges() { 102 synchronized (this) { 103 if (mReceiver != null) { 104 mContext.unregisterReceiver(mReceiver); 105 mReceiver = null; 106 } 107 } 108 } 109 110 /** 111 * Value type that describes a Service. The information within can be used 112 * to bind to the service. 113 */ 114 public static class ServiceInfo<V> { 115 public final V type; 116 public final ComponentName componentName; 117 public final int uid; 118 119 private ServiceInfo(V type, ComponentName componentName, int uid) { 120 this.type = type; 121 this.componentName = componentName; 122 this.uid = uid; 123 } 124 125 public String toString() { 126 return "ServiceInfo: " + type + ", " + componentName; 127 } 128 } 129 130 /** 131 * Accessor for the registered authenticators. 132 * @param type the account type of the authenticator 133 * @return the AuthenticatorInfo that matches the account type or null if none is present 134 */ 135 public ServiceInfo<V> getServiceInfo(V type) { 136 if (mServices == null) { 137 maybeRegisterForPackageChanges(); 138 mServices = generateServicesMap(); 139 } 140 return mServices.get(type); 141 } 142 143 /** 144 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all 145 * registered authenticators. 146 */ 147 public Collection<ServiceInfo<V>> getAllServices() { 148 if (mServices == null) { 149 maybeRegisterForPackageChanges(); 150 mServices = generateServicesMap(); 151 } 152 return Collections.unmodifiableCollection(mServices.values()); 153 } 154 155 /** 156 * Stops the monitoring of package additions, removals and changes. 157 */ 158 public void close() { 159 maybeUnregisterForPackageChanges(); 160 } 161 162 protected void finalize() throws Throwable { 163 synchronized (this) { 164 if (mReceiver != null) { 165 Log.e(TAG, "RegisteredServicesCache finalized without being closed"); 166 } 167 } 168 close(); 169 super.finalize(); 170 } 171 172 private Map<V, ServiceInfo<V>> generateServicesMap() { 173 Map<V, ServiceInfo<V>> services = Maps.newHashMap(); 174 PackageManager pm = mContext.getPackageManager(); 175 176 List<ResolveInfo> resolveInfos = 177 pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA); 178 179 for (ResolveInfo resolveInfo : resolveInfos) { 180 try { 181 ServiceInfo<V> info = parseServiceInfo(resolveInfo); 182 if (info != null) { 183 services.put(info.type, info); 184 } else { 185 Log.w(TAG, "Unable to load input method " + resolveInfo.toString()); 186 } 187 } catch (XmlPullParserException e) { 188 Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); 189 } catch (IOException e) { 190 Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); 191 } 192 } 193 194 return services; 195 } 196 197 private ServiceInfo<V> parseServiceInfo(ResolveInfo service) 198 throws XmlPullParserException, IOException { 199 android.content.pm.ServiceInfo si = service.serviceInfo; 200 ComponentName componentName = new ComponentName(si.packageName, si.name); 201 202 PackageManager pm = mContext.getPackageManager(); 203 204 XmlResourceParser parser = null; 205 try { 206 parser = si.loadXmlMetaData(pm, mMetaDataName); 207 if (parser == null) { 208 throw new XmlPullParserException("No " + mMetaDataName + " meta-data"); 209 } 210 211 AttributeSet attrs = Xml.asAttributeSet(parser); 212 213 int type; 214 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 215 && type != XmlPullParser.START_TAG) { 216 } 217 218 String nodeName = parser.getName(); 219 if (!mAttributesName.equals(nodeName)) { 220 throw new XmlPullParserException( 221 "Meta-data does not start with " + mAttributesName + " tag"); 222 } 223 224 V v = parseServiceAttributes(si.packageName, attrs); 225 if (v == null) { 226 return null; 227 } 228 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo; 229 final ApplicationInfo applicationInfo = serviceInfo.applicationInfo; 230 final int uid = applicationInfo.uid; 231 return new ServiceInfo<V>(v, componentName, uid); 232 } finally { 233 if (parser != null) parser.close(); 234 } 235 } 236 237 public abstract V parseServiceAttributes(String packageName, AttributeSet attrs); 238} 239