blob: d7c4439f82588eb2d3198d887cabee1126cde155 [file] [log] [blame]
Fred Quintana718d8a22009-04-29 17:53:20 -07001/*
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
Hongming Jin8e2bfc12018-05-30 11:01:06 -070019import android.Manifest;
Ng Zhi An1033db82019-01-24 13:45:57 -080020import android.annotation.Nullable;
Mathew Inwood1c77a112018-08-14 14:06:26 +010021import android.annotation.UnsupportedAppUsage;
Fred Quintana718d8a22009-04-29 17:53:20 -070022import android.content.BroadcastReceiver;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070023import android.content.ComponentName;
24import android.content.Context;
Fred Quintana718d8a22009-04-29 17:53:20 -070025import android.content.Intent;
26import android.content.IntentFilter;
Dianne Hackborn20cb56e2010-03-04 00:58:29 -080027import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.res.Resources;
Fred Quintana718d8a22009-04-29 17:53:20 -070029import android.content.res.XmlResourceParser;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080030import android.os.Environment;
31import android.os.Handler;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070032import android.os.UserHandle;
Fyodor Kupolov259e7612015-02-11 14:13:34 -080033import android.os.UserManager;
Dianne Hackborn39606a02012-07-31 17:54:35 -070034import android.util.AtomicFile;
Fred Quintana718d8a22009-04-29 17:53:20 -070035import android.util.AttributeSet;
Fyodor Kupolov81446482016-08-24 11:27:49 -070036import android.util.IntArray;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070037import android.util.Log;
38import android.util.Slog;
39import android.util.SparseArray;
Fred Quintana718d8a22009-04-29 17:53:20 -070040import android.util.Xml;
41
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080042import com.android.internal.annotations.GuardedBy;
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080043import com.android.internal.annotations.VisibleForTesting;
Amith Yamasani460a7b42015-02-06 14:41:40 -080044import com.android.internal.util.ArrayUtils;
Dianne Hackborn2269d1572010-02-24 19:54:22 -080045import com.android.internal.util.FastXmlSerializer;
Hongming Jin8e2bfc12018-05-30 11:01:06 -070046
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080047import com.google.android.collect.Lists;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070048import com.google.android.collect.Maps;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080049
Hongming Jin8e2bfc12018-05-30 11:01:06 -070050import libcore.io.IoUtils;
51
Fred Quintana718d8a22009-04-29 17:53:20 -070052import org.xmlpull.v1.XmlPullParser;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070053import org.xmlpull.v1.XmlPullParserException;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080054import org.xmlpull.v1.XmlSerializer;
Fred Quintana718d8a22009-04-29 17:53:20 -070055
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070056import java.io.File;
57import java.io.FileDescriptor;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070058import java.io.FileOutputStream;
59import java.io.IOException;
Fyodor Kupolov259e7612015-02-11 14:13:34 -080060import java.io.InputStream;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070061import java.io.PrintWriter;
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +010062import java.nio.charset.StandardCharsets;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070063import java.util.ArrayList;
Fyodor Kupolov81446482016-08-24 11:27:49 -070064import java.util.Arrays;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070065import java.util.Collection;
66import java.util.Collections;
67import java.util.List;
68import java.util.Map;
69
Fred Quintana718d8a22009-04-29 17:53:20 -070070/**
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070071 * Cache of registered services. This cache is lazily built by interrogating
72 * {@link PackageManager} on a per-user basis. It's updated as packages are
73 * added, removed and changed. Users are responsible for calling
74 * {@link #invalidateCache(int)} when a user is started, since
75 * {@link PackageManager} broadcasts aren't sent for stopped users.
76 * <p>
77 * The services are referred to by type V and are made available via the
78 * {@link #getServiceInfo} method.
Hongming Jin8e2bfc12018-05-30 11:01:06 -070079 *
Fred Quintana718d8a22009-04-29 17:53:20 -070080 * @hide
81 */
82public abstract class RegisteredServicesCache<V> {
83 private static final String TAG = "PackageManager";
Dianne Hackborn40e9f292012-11-27 19:12:23 -080084 private static final boolean DEBUG = false;
Fyodor Kupolov259e7612015-02-11 14:13:34 -080085 protected static final String REGISTERED_SERVICES_DIR = "registered_services";
Fred Quintana718d8a22009-04-29 17:53:20 -070086
87 public final Context mContext;
88 private final String mInterfaceName;
89 private final String mMetaDataName;
90 private final String mAttributesName;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -080091 private final XmlSerializerAndParser<V> mSerializerAndParser;
Fred Quintana718d8a22009-04-29 17:53:20 -070092
Amith Yamasani37a40c22015-06-17 13:25:42 -070093 protected final Object mServicesLock = new Object();
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070094
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080095 @GuardedBy("mServicesLock")
Dianne Hackbornf4bf0ae2013-05-20 18:42:16 -070096 private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
Jeff Sharkey6ab72d72012-10-08 16:44:37 -070097
98 private static class UserServices<V> {
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -080099 @GuardedBy("mServicesLock")
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800100 final Map<V, Integer> persistentServices = Maps.newHashMap();
Jeff Sharkey8b2c3a142012-11-12 11:45:05 -0800101 @GuardedBy("mServicesLock")
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800102 Map<V, ServiceInfo<V>> services = null;
103 @GuardedBy("mServicesLock")
104 boolean mPersistentServicesFileDidNotExist = true;
Hongming Jin8e2bfc12018-05-30 11:01:06 -0700105 @GuardedBy("mServicesLock")
106 boolean mBindInstantServiceAllowed = false;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700107 }
108
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800109 @GuardedBy("mServicesLock")
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700110 private UserServices<V> findOrCreateUserLocked(int userId) {
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800111 return findOrCreateUserLocked(userId, true);
112 }
113
114 @GuardedBy("mServicesLock")
115 private UserServices<V> findOrCreateUserLocked(int userId, boolean loadFromFileIfNew) {
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700116 UserServices<V> services = mUserServices.get(userId);
117 if (services == null) {
118 services = new UserServices<V>();
119 mUserServices.put(userId, services);
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800120 if (loadFromFileIfNew && mSerializerAndParser != null) {
121 // Check if user exists and try loading data from file
122 // clear existing data if there was an error during migration
123 UserInfo user = getUser(userId);
124 if (user != null) {
125 AtomicFile file = createFileForUser(user.id);
126 if (file.getBaseFile().exists()) {
127 if (DEBUG) {
128 Slog.i(TAG, String.format("Loading u%s data from %s", user.id, file));
129 }
130 InputStream is = null;
131 try {
132 is = file.openRead();
133 readPersistentServicesLocked(is);
134 } catch (Exception e) {
135 Log.w(TAG, "Error reading persistent services for user " + user.id, e);
136 } finally {
137 IoUtils.closeQuietly(is);
138 }
139 }
140 }
141 }
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700142 }
143 return services;
144 }
Fred Quintana3ecd5f42009-09-17 12:42:35 -0700145
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800146 // the listener and handler are synchronized on "this" and must be updated together
147 private RegisteredServicesCacheListener<V> mListener;
148 private Handler mHandler;
Fred Quintana718d8a22009-04-29 17:53:20 -0700149
Mathew Inwood1c77a112018-08-14 14:06:26 +0100150 @UnsupportedAppUsage
Fred Quintana718d8a22009-04-29 17:53:20 -0700151 public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800152 String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
Fred Quintana718d8a22009-04-29 17:53:20 -0700153 mContext = context;
154 mInterfaceName = interfaceName;
155 mMetaDataName = metaDataName;
156 mAttributesName = attributeName;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800157 mSerializerAndParser = serializerAndParser;
158
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800159 migrateIfNecessaryLocked();
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800160
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800161 IntentFilter intentFilter = new IntentFilter();
162 intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
163 intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
164 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
165 intentFilter.addDataScheme("package");
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700166 mContext.registerReceiverAsUser(mPackageReceiver, UserHandle.ALL, intentFilter, null, null);
167
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800168 // Register for events related to sdcard installation.
169 IntentFilter sdFilter = new IntentFilter();
Suchi Amalapurapub56ae202010-02-04 22:51:07 -0800170 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
171 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700172 mContext.registerReceiver(mExternalReceiver, sdFilter);
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800173
174 // Register for user-related events
175 IntentFilter userFilter = new IntentFilter();
176 sdFilter.addAction(Intent.ACTION_USER_REMOVED);
177 mContext.registerReceiver(mUserRemovedReceiver, userFilter);
Fred Quintana718d8a22009-04-29 17:53:20 -0700178 }
179
Ng Zhi An1033db82019-01-24 13:45:57 -0800180 @VisibleForTesting
181 protected void handlePackageEvent(Intent intent, int userId) {
Christopher Tate0598d702014-03-11 18:16:46 -0700182 // Don't regenerate the services map when the package is removed or its
183 // ASEC container unmounted as a step in replacement. The subsequent
184 // _ADDED / _AVAILABLE call will regenerate the map in the final state.
185 final String action = intent.getAction();
186 // it's a new-component action if it isn't some sort of removal
187 final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action)
188 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action);
189 // if it's a removal, is it part of an update-in-place step?
190 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
191
192 if (isRemoval && replacing) {
193 // package is going away, but it's the middle of an upgrade: keep the current
194 // state and do nothing here. This clause is intentionally empty.
195 } else {
Amith Yamasani460a7b42015-02-06 14:41:40 -0800196 int[] uids = null;
Christopher Tate0598d702014-03-11 18:16:46 -0700197 // either we're adding/changing, or it's a removal without replacement, so
Amith Yamasani460a7b42015-02-06 14:41:40 -0800198 // we need to update the set of available services
199 if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)
200 || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
201 uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
202 } else {
203 int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
204 if (uid > 0) {
205 uids = new int[] { uid };
206 }
207 }
208 generateServicesMap(uids, userId);
Christopher Tate0598d702014-03-11 18:16:46 -0700209 }
210 }
211
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700212 private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
213 @Override
214 public void onReceive(Context context, Intent intent) {
215 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
216 if (uid != -1) {
Christopher Tate0598d702014-03-11 18:16:46 -0700217 handlePackageEvent(intent, UserHandle.getUserId(uid));
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700218 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800219 }
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700220 };
221
222 private final BroadcastReceiver mExternalReceiver = new BroadcastReceiver() {
223 @Override
224 public void onReceive(Context context, Intent intent) {
225 // External apps can't coexist with multi-user, so scan owner
Xiaohui Chen98404fd2015-08-17 16:09:02 -0700226 handlePackageEvent(intent, UserHandle.USER_SYSTEM);
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700227 }
228 };
229
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800230 private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
231 @Override
232 public void onReceive(Context context, Intent intent) {
233 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
234 if (DEBUG) {
235 Slog.d(TAG, "u" + userId + " removed - cleaning up");
236 }
237 onUserRemoved(userId);
238 }
239 };
240
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700241 public void invalidateCache(int userId) {
242 synchronized (mServicesLock) {
Ng Zhi An1033db82019-01-24 13:45:57 -0800243 if (DEBUG) {
244 Slog.d(TAG, "invalidating cache for " + userId + " " + mInterfaceName);
245 }
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700246 final UserServices<V> user = findOrCreateUserLocked(userId);
247 user.services = null;
Amith Yamasani37a40c22015-06-17 13:25:42 -0700248 onServicesChangedLocked(userId);
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700249 }
250 }
251
252 public void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId) {
253 synchronized (mServicesLock) {
254 final UserServices<V> user = findOrCreateUserLocked(userId);
255 if (user.services != null) {
256 fout.println("RegisteredServicesCache: " + user.services.size() + " services");
257 for (ServiceInfo<?> info : user.services.values()) {
258 fout.println(" " + info);
259 }
260 } else {
261 fout.println("RegisteredServicesCache: services not loaded");
262 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700263 }
264 }
265
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800266 public RegisteredServicesCacheListener<V> getListener() {
Fred Quintana718d8a22009-04-29 17:53:20 -0700267 synchronized (this) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800268 return mListener;
Fred Quintana718d8a22009-04-29 17:53:20 -0700269 }
270 }
271
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800272 public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
273 if (handler == null) {
274 handler = new Handler(mContext.getMainLooper());
Fred Quintana718d8a22009-04-29 17:53:20 -0700275 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800276 synchronized (this) {
277 mHandler = handler;
278 mListener = listener;
279 }
280 }
281
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700282 private void notifyListener(final V type, final int userId, final boolean removed) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800283 if (DEBUG) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800284 Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
285 }
286 RegisteredServicesCacheListener<V> listener;
Hongming Jin8e2bfc12018-05-30 11:01:06 -0700287 Handler handler;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800288 synchronized (this) {
289 listener = mListener;
290 handler = mHandler;
291 }
292 if (listener == null) {
293 return;
294 }
Hongming Jin8e2bfc12018-05-30 11:01:06 -0700295
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800296 final RegisteredServicesCacheListener<V> listener2 = listener;
297 handler.post(new Runnable() {
298 public void run() {
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700299 listener2.onServiceChanged(type, userId, removed);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800300 }
301 });
Fred Quintana718d8a22009-04-29 17:53:20 -0700302 }
303
304 /**
305 * Value type that describes a Service. The information within can be used
306 * to bind to the service.
307 */
308 public static class ServiceInfo<V> {
Mathew Inwood1c77a112018-08-14 14:06:26 +0100309 @UnsupportedAppUsage
Fred Quintana718d8a22009-04-29 17:53:20 -0700310 public final V type;
Jeff Sharkey9d8a1042015-12-03 17:56:20 -0700311 public final ComponentInfo componentInfo;
Mathew Inwood1c77a112018-08-14 14:06:26 +0100312 @UnsupportedAppUsage
Fred Quintana718d8a22009-04-29 17:53:20 -0700313 public final ComponentName componentName;
Mathew Inwood1c77a112018-08-14 14:06:26 +0100314 @UnsupportedAppUsage
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700315 public final int uid;
Fred Quintana718d8a22009-04-29 17:53:20 -0700316
Fred Quintana56285a62010-12-02 14:20:51 -0800317 /** @hide */
Jeff Sharkey9d8a1042015-12-03 17:56:20 -0700318 public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName) {
Fred Quintana718d8a22009-04-29 17:53:20 -0700319 this.type = type;
Jeff Sharkey9d8a1042015-12-03 17:56:20 -0700320 this.componentInfo = componentInfo;
Fred Quintana718d8a22009-04-29 17:53:20 -0700321 this.componentName = componentName;
Jeff Sharkey9d8a1042015-12-03 17:56:20 -0700322 this.uid = (componentInfo != null) ? componentInfo.applicationInfo.uid : -1;
Fred Quintana718d8a22009-04-29 17:53:20 -0700323 }
324
Jeff Hamiltonc42c0dd2009-09-03 09:08:30 -0500325 @Override
Fred Quintana718d8a22009-04-29 17:53:20 -0700326 public String toString() {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800327 return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
Fred Quintana718d8a22009-04-29 17:53:20 -0700328 }
329 }
330
331 /**
332 * Accessor for the registered authenticators.
333 * @param type the account type of the authenticator
334 * @return the AuthenticatorInfo that matches the account type or null if none is present
335 */
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700336 public ServiceInfo<V> getServiceInfo(V type, int userId) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800337 synchronized (mServicesLock) {
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700338 // Find user and lazily populate cache
339 final UserServices<V> user = findOrCreateUserLocked(userId);
340 if (user.services == null) {
Amith Yamasani460a7b42015-02-06 14:41:40 -0800341 generateServicesMap(null, userId);
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700342 }
343 return user.services.get(type);
Fred Quintana718d8a22009-04-29 17:53:20 -0700344 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700345 }
346
347 /**
348 * @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
349 * registered authenticators.
350 */
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700351 public Collection<ServiceInfo<V>> getAllServices(int userId) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800352 synchronized (mServicesLock) {
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700353 // Find user and lazily populate cache
354 final UserServices<V> user = findOrCreateUserLocked(userId);
355 if (user.services == null) {
Amith Yamasani460a7b42015-02-06 14:41:40 -0800356 generateServicesMap(null, userId);
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700357 }
Jeff Sharkeya706e2f2012-10-16 12:02:42 -0700358 return Collections.unmodifiableCollection(
359 new ArrayList<ServiceInfo<V>>(user.services.values()));
Fred Quintana718d8a22009-04-29 17:53:20 -0700360 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700361 }
362
Fyodor Kupolov81446482016-08-24 11:27:49 -0700363 public void updateServices(int userId) {
364 if (DEBUG) {
365 Slog.d(TAG, "updateServices u" + userId);
366 }
367 List<ServiceInfo<V>> allServices;
368 synchronized (mServicesLock) {
369 final UserServices<V> user = findOrCreateUserLocked(userId);
370 // If services haven't been initialized yet - no updates required
371 if (user.services == null) {
372 return;
373 }
374 allServices = new ArrayList<>(user.services.values());
375 }
376 IntArray updatedUids = null;
377 for (ServiceInfo<V> service : allServices) {
Dianne Hackborn3accca02013-09-20 09:32:11 -0700378 long versionCode = service.componentInfo.applicationInfo.versionCode;
Fyodor Kupolov81446482016-08-24 11:27:49 -0700379 String pkg = service.componentInfo.packageName;
380 ApplicationInfo newAppInfo = null;
381 try {
382 newAppInfo = mContext.getPackageManager().getApplicationInfoAsUser(pkg, 0, userId);
383 } catch (NameNotFoundException e) {
384 // Package uninstalled - treat as null app info
385 }
386 // If package updated or removed
387 if ((newAppInfo == null) || (newAppInfo.versionCode != versionCode)) {
388 if (DEBUG) {
389 Slog.d(TAG, "Package " + pkg + " uid=" + service.uid
390 + " updated. New appInfo: " + newAppInfo);
391 }
392 if (updatedUids == null) {
393 updatedUids = new IntArray();
394 }
395 updatedUids.add(service.uid);
396 }
397 }
398 if (updatedUids != null && updatedUids.size() > 0) {
399 int[] updatedUidsArray = updatedUids.toArray();
400 generateServicesMap(updatedUidsArray, userId);
401 }
402 }
403
Hongming Jin8e2bfc12018-05-30 11:01:06 -0700404 /**
405 * @return whether the binding to service is allowed for instant apps.
406 */
407 public boolean getBindInstantServiceAllowed(int userId) {
408 mContext.enforceCallingOrSelfPermission(
409 Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
410 "getBindInstantServiceAllowed");
411
412 synchronized (mServicesLock) {
413 final UserServices<V> user = findOrCreateUserLocked(userId);
414 return user.mBindInstantServiceAllowed;
415 }
416 }
417
418 /**
419 * Set whether the binding to service is allowed or not for instant apps.
420 */
421 public void setBindInstantServiceAllowed(int userId, boolean allowed) {
422 mContext.enforceCallingOrSelfPermission(
423 Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
424 "setBindInstantServiceAllowed");
425
426 synchronized (mServicesLock) {
427 final UserServices<V> user = findOrCreateUserLocked(userId);
428 user.mBindInstantServiceAllowed = allowed;
429 }
430 }
431
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800432 @VisibleForTesting
433 protected boolean inSystemImage(int callerUid) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800434 String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
liulvpingbf76ee72016-09-08 09:16:48 +0800435 if (packages != null) {
436 for (String name : packages) {
437 try {
438 PackageInfo packageInfo =
439 mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
440 if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
441 return true;
442 }
443 } catch (PackageManager.NameNotFoundException e) {
444 return false;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800445 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800446 }
447 }
448 return false;
449 }
450
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800451 @VisibleForTesting
452 protected List<ResolveInfo> queryIntentServices(int userId) {
453 final PackageManager pm = mContext.getPackageManager();
Hongming Jin8e2bfc12018-05-30 11:01:06 -0700454 int flags = PackageManager.GET_META_DATA
455 | PackageManager.MATCH_DIRECT_BOOT_AWARE
456 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
457 synchronized (mServicesLock) {
458 final UserServices<V> user = findOrCreateUserLocked(userId);
459 if (user.mBindInstantServiceAllowed) {
460 flags |= PackageManager.MATCH_INSTANT;
461 }
462 }
463 return pm.queryIntentServicesAsUser(new Intent(mInterfaceName), flags, userId);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800464 }
465
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700466 /**
467 * Populate {@link UserServices#services} by scanning installed packages for
468 * given {@link UserHandle}.
Amith Yamasani460a7b42015-02-06 14:41:40 -0800469 * @param changedUids the array of uids that have been affected, as mentioned in the broadcast
470 * or null to assume that everything is affected.
471 * @param userId the user for whom to update the services map.
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700472 */
Ng Zhi An1033db82019-01-24 13:45:57 -0800473 private void generateServicesMap(@Nullable int[] changedUids, int userId) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800474 if (DEBUG) {
Fyodor Kupolov81446482016-08-24 11:27:49 -0700475 Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = "
476 + Arrays.toString(changedUids));
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800477 }
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700478
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800479 synchronized (mServicesLock) {
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700480 final UserServices<V> user = findOrCreateUserLocked(userId);
Ng Zhi An1033db82019-01-24 13:45:57 -0800481 final boolean cacheInvalid = user.services == null;
482 if (cacheInvalid) {
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700483 user.services = Maps.newHashMap();
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800484 }
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700485
Ng Zhi An1033db82019-01-24 13:45:57 -0800486 final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
487 final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
488
489 for (ResolveInfo resolveInfo : resolveInfos) {
490 try {
491 // when changedUids == null, we want to do a rescan of everything, this means
492 // it's the initial scan, and containsUid will trivially return true
493 // when changedUids != null, we got here because a package changed, but
494 // invalidateCache could have been called (thus user.services == null), and we
495 // should query from PackageManager again
496 if (!cacheInvalid
497 && !containsUid(
498 changedUids, resolveInfo.serviceInfo.applicationInfo.uid)) {
499 if (DEBUG) {
500 Slog.d(TAG, "Skipping parseServiceInfo for " + resolveInfo);
501 }
502 continue;
503 }
504 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
505 if (info == null) {
506 Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
507 continue;
508 }
509 serviceInfos.add(info);
510 } catch (XmlPullParserException | IOException e) {
511 Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
512 }
513 }
514
Alon Albert3fa51e32010-11-11 09:24:04 -0800515 StringBuilder changes = new StringBuilder();
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800516 boolean changed = false;
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800517 for (ServiceInfo<V> info : serviceInfos) {
518 // four cases:
519 // - doesn't exist yet
520 // - add, notify user that it was added
521 // - exists and the UID is the same
522 // - replace, don't notify user
523 // - exists, the UID is different, and the new one is not a system package
524 // - ignore
525 // - exists, the UID is different, and the new one is a system package
526 // - add, notify user that it was added
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700527 Integer previousUid = user.persistentServices.get(info.type);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800528 if (previousUid == null) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800529 if (DEBUG) {
530 changes.append(" New service added: ").append(info).append("\n");
531 }
532 changed = true;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700533 user.services.put(info.type, info);
534 user.persistentServices.put(info.type, info.uid);
Ng Zhi An1033db82019-01-24 13:45:57 -0800535 if (!(user.mPersistentServicesFileDidNotExist && cacheInvalid)) {
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700536 notifyListener(info.type, userId, false /* removed */);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800537 }
538 } else if (previousUid == info.uid) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800539 if (DEBUG) {
Alon Albert3fa51e32010-11-11 09:24:04 -0800540 changes.append(" Existing service (nop): ").append(info).append("\n");
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800541 }
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700542 user.services.put(info.type, info);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800543 } else if (inSystemImage(info.uid)
544 || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800545 if (DEBUG) {
546 if (inSystemImage(info.uid)) {
547 changes.append(" System service replacing existing: ").append(info)
548 .append("\n");
549 } else {
550 changes.append(" Existing service replacing a removed service: ")
551 .append(info).append("\n");
552 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800553 }
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800554 changed = true;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700555 user.services.put(info.type, info);
556 user.persistentServices.put(info.type, info.uid);
557 notifyListener(info.type, userId, false /* removed */);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800558 } else {
559 // ignore
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800560 if (DEBUG) {
561 changes.append(" Existing service with new uid ignored: ").append(info)
562 .append("\n");
563 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800564 }
565 }
566
567 ArrayList<V> toBeRemoved = Lists.newArrayList();
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700568 for (V v1 : user.persistentServices.keySet()) {
Amith Yamasani460a7b42015-02-06 14:41:40 -0800569 // Remove a persisted service that's not in the currently available services list.
570 // And only if it is in the list of changedUids.
571 if (!containsType(serviceInfos, v1)
572 && containsUid(changedUids, user.persistentServices.get(v1))) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800573 toBeRemoved.add(v1);
574 }
575 }
576 for (V v1 : toBeRemoved) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800577 if (DEBUG) {
578 changes.append(" Service removed: ").append(v1).append("\n");
579 }
580 changed = true;
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700581 user.persistentServices.remove(v1);
Amith Yamasani460a7b42015-02-06 14:41:40 -0800582 user.services.remove(v1);
Jeff Sharkey6ab72d72012-10-08 16:44:37 -0700583 notifyListener(v1, userId, true /* removed */);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800584 }
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800585 if (DEBUG) {
Amith Yamasani460a7b42015-02-06 14:41:40 -0800586 Log.d(TAG, "user.services=");
587 for (V v : user.services.keySet()) {
588 Log.d(TAG, " " + v + " " + user.services.get(v));
589 }
590 Log.d(TAG, "user.persistentServices=");
591 for (V v : user.persistentServices.keySet()) {
592 Log.d(TAG, " " + v + " " + user.persistentServices.get(v));
593 }
594 }
595 if (DEBUG) {
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800596 if (changes.length() > 0) {
Dianne Hackborn4428e172012-08-24 17:43:05 -0700597 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
598 serviceInfos.size() + " services:\n" + changes);
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800599 } else {
Dianne Hackborn4428e172012-08-24 17:43:05 -0700600 Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
601 serviceInfos.size() + " services unchanged");
602 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800603 }
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800604 if (changed) {
Amith Yamasani37a40c22015-06-17 13:25:42 -0700605 onServicesChangedLocked(userId);
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800606 writePersistentServicesLocked(user, userId);
Dianne Hackborn40e9f292012-11-27 19:12:23 -0800607 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800608 }
609 }
610
Amith Yamasani37a40c22015-06-17 13:25:42 -0700611 protected void onServicesChangedLocked(int userId) {
612 // Feel free to override
613 }
614
Amith Yamasani460a7b42015-02-06 14:41:40 -0800615 /**
616 * Returns true if the list of changed uids is null (wildcard) or the specified uid
617 * is contained in the list of changed uids.
618 */
619 private boolean containsUid(int[] changedUids, int uid) {
620 return changedUids == null || ArrayUtils.contains(changedUids, uid);
621 }
622
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800623 private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
624 for (int i = 0, N = serviceInfos.size(); i < N; i++) {
625 if (serviceInfos.get(i).type.equals(type)) {
626 return true;
627 }
628 }
629
630 return false;
631 }
632
633 private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
634 for (int i = 0, N = serviceInfos.size(); i < N; i++) {
635 final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
636 if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
637 return true;
638 }
639 }
640
641 return false;
Fred Quintana718d8a22009-04-29 17:53:20 -0700642 }
643
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800644 @VisibleForTesting
645 protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
Fred Quintana718d8a22009-04-29 17:53:20 -0700646 throws XmlPullParserException, IOException {
647 android.content.pm.ServiceInfo si = service.serviceInfo;
648 ComponentName componentName = new ComponentName(si.packageName, si.name);
649
650 PackageManager pm = mContext.getPackageManager();
651
652 XmlResourceParser parser = null;
653 try {
654 parser = si.loadXmlMetaData(pm, mMetaDataName);
655 if (parser == null) {
656 throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
657 }
658
659 AttributeSet attrs = Xml.asAttributeSet(parser);
660
661 int type;
662 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
663 && type != XmlPullParser.START_TAG) {
664 }
665
666 String nodeName = parser.getName();
667 if (!mAttributesName.equals(nodeName)) {
668 throw new XmlPullParserException(
669 "Meta-data does not start with " + mAttributesName + " tag");
670 }
671
Dianne Hackborn20cb56e2010-03-04 00:58:29 -0800672 V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
673 si.packageName, attrs);
Fred Quintana718d8a22009-04-29 17:53:20 -0700674 if (v == null) {
675 return null;
676 }
Fred Quintanad4a1d2e2009-07-16 16:36:38 -0700677 final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
Jeff Sharkey9d8a1042015-12-03 17:56:20 -0700678 return new ServiceInfo<V>(v, serviceInfo, componentName);
Dianne Hackborn20cb56e2010-03-04 00:58:29 -0800679 } catch (NameNotFoundException e) {
680 throw new XmlPullParserException(
681 "Unable to load resources for pacakge " + si.packageName);
Fred Quintana718d8a22009-04-29 17:53:20 -0700682 } finally {
683 if (parser != null) parser.close();
684 }
685 }
686
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800687 /**
688 * Read all sync status back in to the initial engine state.
689 */
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800690 private void readPersistentServicesLocked(InputStream is)
691 throws XmlPullParserException, IOException {
692 XmlPullParser parser = Xml.newPullParser();
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +0100693 parser.setInput(is, StandardCharsets.UTF_8.name());
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800694 int eventType = parser.getEventType();
695 while (eventType != XmlPullParser.START_TAG
696 && eventType != XmlPullParser.END_DOCUMENT) {
697 eventType = parser.next();
698 }
699 String tagName = parser.getName();
700 if ("services".equals(tagName)) {
701 eventType = parser.next();
702 do {
703 if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
704 tagName = parser.getName();
705 if ("service".equals(tagName)) {
706 V service = mSerializerAndParser.createFromXml(parser);
707 if (service == null) {
708 break;
709 }
710 String uidString = parser.getAttributeValue(null, "uid");
711 final int uid = Integer.parseInt(uidString);
712 final int userId = UserHandle.getUserId(uid);
713 final UserServices<V> user = findOrCreateUserLocked(userId,
714 false /*loadFromFileIfNew*/) ;
715 user.persistentServices.put(service, uid);
716 }
717 }
718 eventType = parser.next();
719 } while (eventType != XmlPullParser.END_DOCUMENT);
720 }
721 }
722
723 private void migrateIfNecessaryLocked() {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800724 if (mSerializerAndParser == null) {
725 return;
726 }
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800727 File systemDir = new File(getDataDirectory(), "system");
728 File syncDir = new File(systemDir, REGISTERED_SERVICES_DIR);
729 AtomicFile oldFile = new AtomicFile(new File(syncDir, mInterfaceName + ".xml"));
730 boolean oldFileExists = oldFile.getBaseFile().exists();
731
732 if (oldFileExists) {
733 File marker = new File(syncDir, mInterfaceName + ".xml.migrated");
734 // if not migrated, perform the migration and add a marker
735 if (!marker.exists()) {
736 if (DEBUG) {
737 Slog.i(TAG, "Marker file " + marker + " does not exist - running migration");
738 }
739 InputStream is = null;
740 try {
741 is = oldFile.openRead();
742 mUserServices.clear();
743 readPersistentServicesLocked(is);
744 } catch (Exception e) {
745 Log.w(TAG, "Error reading persistent services, starting from scratch", e);
746 } finally {
747 IoUtils.closeQuietly(is);
748 }
749 try {
750 for (UserInfo user : getUsers()) {
751 UserServices<V> userServices = mUserServices.get(user.id);
752 if (userServices != null) {
753 if (DEBUG) {
754 Slog.i(TAG, "Migrating u" + user.id + " services "
755 + userServices.persistentServices);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800756 }
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800757 writePersistentServicesLocked(userServices, user.id);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800758 }
759 }
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800760 marker.createNewFile();
761 } catch (Exception e) {
762 Log.w(TAG, "Migration failed", e);
763 }
764 // Migration is complete and we don't need to keep data for all users anymore,
765 // It will be loaded from a new location when requested
766 mUserServices.clear();
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800767 }
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800768 }
769 }
770
771 /**
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800772 * Writes services of a specified user to the file.
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800773 */
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800774 private void writePersistentServicesLocked(UserServices<V> user, int userId) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800775 if (mSerializerAndParser == null) {
776 return;
777 }
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800778 AtomicFile atomicFile = createFileForUser(userId);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800779 FileOutputStream fos = null;
780 try {
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800781 fos = atomicFile.startWrite();
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800782 XmlSerializer out = new FastXmlSerializer();
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +0100783 out.setOutput(fos, StandardCharsets.UTF_8.name());
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800784 out.startDocument(null, true);
785 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
786 out.startTag(null, "services");
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800787 for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
788 out.startTag(null, "service");
789 out.attribute(null, "uid", Integer.toString(service.getValue()));
790 mSerializerAndParser.writeAsXml(service.getKey(), out);
791 out.endTag(null, "service");
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800792 }
793 out.endTag(null, "services");
794 out.endDocument();
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800795 atomicFile.finishWrite(fos);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800796 } catch (IOException e1) {
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800797 Log.w(TAG, "Error writing accounts", e1);
798 if (fos != null) {
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800799 atomicFile.failWrite(fos);
Fred Quintana5ebbb4a2009-11-09 15:42:20 -0800800 }
801 }
802 }
803
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800804 @VisibleForTesting
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800805 protected void onUserRemoved(int userId) {
Amith Yamasani37a40c22015-06-17 13:25:42 -0700806 synchronized (mServicesLock) {
807 mUserServices.remove(userId);
808 }
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800809 }
810
811 @VisibleForTesting
812 protected List<UserInfo> getUsers() {
813 return UserManager.get(mContext).getUsers(true);
814 }
815
816 @VisibleForTesting
817 protected UserInfo getUser(int userId) {
818 return UserManager.get(mContext).getUserInfo(userId);
819 }
820
821 private AtomicFile createFileForUser(int userId) {
822 File userDir = getUserSystemDirectory(userId);
823 File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml");
824 return new AtomicFile(userFile);
825 }
826
827 @VisibleForTesting
828 protected File getUserSystemDirectory(int userId) {
829 return Environment.getUserSystemDirectory(userId);
830 }
831
832 @VisibleForTesting
833 protected File getDataDirectory() {
834 return Environment.getDataDirectory();
835 }
836
837 @VisibleForTesting
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800838 protected Map<V, Integer> getPersistentServices(int userId) {
839 return findOrCreateUserLocked(userId).persistentServices;
840 }
841
Dianne Hackborn20cb56e2010-03-04 00:58:29 -0800842 public abstract V parseServiceAttributes(Resources res,
843 String packageName, AttributeSet attrs);
Fred Quintana718d8a22009-04-29 17:53:20 -0700844}