RegisteredServicesCache.java revision 9788976b1465ce982b5ae7c741345edd0ecd9322
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 118 private ServiceInfo(V type, ComponentName componentName) { 119 this.type = type; 120 this.componentName = componentName; 121 } 122 123 public String toString() { 124 return "ServiceInfo: " + type + ", " + componentName; 125 } 126 } 127 128 /** 129 * Accessor for the registered authenticators. 130 * @param type the account type of the authenticator 131 * @return the AuthenticatorInfo that matches the account type or null if none is present 132 */ 133 public ServiceInfo<V> getServiceInfo(V type) { 134 if (mServices == null) { 135 maybeRegisterForPackageChanges(); 136 mServices = generateServicesMap(); 137 } 138 return mServices.get(type); 139 } 140 141 /** 142 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all 143 * registered authenticators. 144 */ 145 public Collection<ServiceInfo<V>> getAllServices() { 146 if (mServices == null) { 147 maybeRegisterForPackageChanges(); 148 mServices = generateServicesMap(); 149 } 150 return Collections.unmodifiableCollection(mServices.values()); 151 } 152 153 /** 154 * Stops the monitoring of package additions, removals and changes. 155 */ 156 public void close() { 157 maybeUnregisterForPackageChanges(); 158 } 159 160 protected void finalize() throws Throwable { 161 synchronized (this) { 162 if (mReceiver != null) { 163 Log.e(TAG, "RegisteredServicesCache finalized without being closed"); 164 } 165 } 166 close(); 167 super.finalize(); 168 } 169 170 private Map<V, ServiceInfo<V>> generateServicesMap() { 171 Map<V, ServiceInfo<V>> services = Maps.newHashMap(); 172 PackageManager pm = mContext.getPackageManager(); 173 174 List<ResolveInfo> resolveInfos = 175 pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA); 176 177 for (ResolveInfo resolveInfo : resolveInfos) { 178 try { 179 ServiceInfo<V> info = parseServiceInfo(resolveInfo); 180 if (info != null) { 181 services.put(info.type, info); 182 } else { 183 Log.w(TAG, "Unable to load input method " + resolveInfo.toString()); 184 } 185 } catch (XmlPullParserException e) { 186 Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); 187 } catch (IOException e) { 188 Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); 189 } 190 } 191 192 return services; 193 } 194 195 private ServiceInfo<V> parseServiceInfo(ResolveInfo service) 196 throws XmlPullParserException, IOException { 197 android.content.pm.ServiceInfo si = service.serviceInfo; 198 ComponentName componentName = new ComponentName(si.packageName, si.name); 199 200 PackageManager pm = mContext.getPackageManager(); 201 202 XmlResourceParser parser = null; 203 try { 204 parser = si.loadXmlMetaData(pm, mMetaDataName); 205 if (parser == null) { 206 throw new XmlPullParserException("No " + mMetaDataName + " meta-data"); 207 } 208 209 AttributeSet attrs = Xml.asAttributeSet(parser); 210 211 int type; 212 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 213 && type != XmlPullParser.START_TAG) { 214 } 215 216 String nodeName = parser.getName(); 217 if (!mAttributesName.equals(nodeName)) { 218 throw new XmlPullParserException( 219 "Meta-data does not start with " + mAttributesName + " tag"); 220 } 221 222 V v = parseServiceAttributes(si.packageName, attrs); 223 if (v == null) { 224 return null; 225 } 226 return new ServiceInfo<V>(v, componentName); 227 } finally { 228 if (parser != null) parser.close(); 229 } 230 } 231 232 public abstract V parseServiceAttributes(String packageName, AttributeSet attrs); 233} 234