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