diff options
| author | Jeff Sharkey <jsharkey@android.com> | 2012-11-15 14:01:46 -0800 |
|---|---|---|
| committer | Amith Yamasani <yamasani@google.com> | 2012-12-11 09:50:50 -0800 |
| commit | 7a96c39c510923ef73bbb06ab20109f0168b8eb1 (patch) | |
| tree | 70fd480a814add0fdcb274540e1f421806020f24 /services/java/com/android/server/content/ContentService.java | |
| parent | 151cb90c6093d5b4371b9367b507f8aa7c1a4370 (diff) | |
| download | frameworks_base-7a96c39c510923ef73bbb06ab20109f0168b8eb1.zip frameworks_base-7a96c39c510923ef73bbb06ab20109f0168b8eb1.tar.gz frameworks_base-7a96c39c510923ef73bbb06ab20109f0168b8eb1.tar.bz2 | |
Move lingering services to services.jar.
This helps reduce the pressure on framework.jar, and makes it clear
that it should only be used by the system_server.
Bug: 7333397
Change-Id: I0858904239535380fbf30562b793e277d8c3f054
Diffstat (limited to 'services/java/com/android/server/content/ContentService.java')
| -rw-r--r-- | services/java/com/android/server/content/ContentService.java | 848 |
1 files changed, 848 insertions, 0 deletions
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java new file mode 100644 index 0000000..3b92338 --- /dev/null +++ b/services/java/com/android/server/content/ContentService.java @@ -0,0 +1,848 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.content; + +import android.Manifest; +import android.accounts.Account; +import android.app.ActivityManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.IContentService; +import android.content.ISyncStatusObserver; +import android.content.PeriodicSync; +import android.content.SyncAdapterType; +import android.content.SyncInfo; +import android.content.SyncStatusInfo; +import android.database.IContentObserver; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.Log; +import android.util.SparseIntArray; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * {@hide} + */ +public final class ContentService extends IContentService.Stub { + private static final String TAG = "ContentService"; + private Context mContext; + private boolean mFactoryTest; + private final ObserverNode mRootNode = new ObserverNode(""); + private SyncManager mSyncManager = null; + private final Object mSyncManagerLock = new Object(); + + private SyncManager getSyncManager() { + synchronized(mSyncManagerLock) { + try { + // Try to create the SyncManager, return null if it fails (e.g. the disk is full). + if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest); + } catch (SQLiteException e) { + Log.e(TAG, "Can't create SyncManager", e); + } + return mSyncManager; + } + } + + @Override + protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP, + "caller doesn't have the DUMP permission"); + + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + if (mSyncManager == null) { + pw.println("No SyncManager created! (Disk full?)"); + } else { + mSyncManager.dump(fd, pw); + } + pw.println(); + pw.println("Observer tree:"); + synchronized (mRootNode) { + int[] counts = new int[2]; + final SparseIntArray pidCounts = new SparseIntArray(); + mRootNode.dumpLocked(fd, pw, args, "", " ", counts, pidCounts); + pw.println(); + ArrayList<Integer> sorted = new ArrayList<Integer>(); + for (int i=0; i<pidCounts.size(); i++) { + sorted.add(pidCounts.keyAt(i)); + } + Collections.sort(sorted, new Comparator<Integer>() { + @Override + public int compare(Integer lhs, Integer rhs) { + int lc = pidCounts.get(lhs); + int rc = pidCounts.get(rhs); + if (lc < rc) { + return 1; + } else if (lc > rc) { + return -1; + } + return 0; + } + + }); + for (int i=0; i<sorted.size(); i++) { + int pid = sorted.get(i); + pw.print(" pid "); pw.print(pid); pw.print(": "); + pw.print(pidCounts.get(pid)); pw.println(" observers"); + } + pw.println(); + pw.print(" Total number of nodes: "); pw.println(counts[0]); + pw.print(" Total number of observers: "); pw.println(counts[1]); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + // The content service only throws security exceptions, so let's + // log all others. + if (!(e instanceof SecurityException)) { + Log.e(TAG, "Content Service Crash", e); + } + throw e; + } + } + + /*package*/ ContentService(Context context, boolean factoryTest) { + mContext = context; + mFactoryTest = factoryTest; + } + + public void systemReady() { + getSyncManager(); + } + + /** + * Register a content observer tied to a specific user's view of the provider. + * @param userHandle the user whose view of the provider is to be observed. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly handled; all other pseudousers are forbidden. + */ + @Override + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer, int userHandle) { + if (observer == null || uri == null) { + throw new IllegalArgumentException("You must pass a valid uri and observer"); + } + + final int callingUser = UserHandle.getCallingUserId(); + if (callingUser != userHandle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "no permission to observe other users' provider view"); + } + + if (userHandle < 0) { + if (userHandle == UserHandle.USER_CURRENT) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for registerContentObserver: " + + userHandle); + } + } + + synchronized (mRootNode) { + mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode, + Binder.getCallingUid(), Binder.getCallingPid(), userHandle); + if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri + + " with notifyForDescendants " + notifyForDescendants); + } + } + + public void registerContentObserver(Uri uri, boolean notifyForDescendants, + IContentObserver observer) { + registerContentObserver(uri, notifyForDescendants, observer, + UserHandle.getCallingUserId()); + } + + public void unregisterContentObserver(IContentObserver observer) { + if (observer == null) { + throw new IllegalArgumentException("You must pass a valid observer"); + } + synchronized (mRootNode) { + mRootNode.removeObserverLocked(observer); + if (false) Log.v(TAG, "Unregistered observer " + observer); + } + } + + /** + * Notify observers of a particular user's view of the provider. + * @param userHandle the user whose view of the provider is to be notified. May be + * the calling user without requiring any permission, otherwise the caller needs to + * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and + * USER_CURRENT are properly interpreted; no other pseudousers are allowed. + */ + @Override + public void notifyChange(Uri uri, IContentObserver observer, + boolean observerWantsSelfNotifications, boolean syncToNetwork, + int userHandle) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle + + " from observer " + observer + ", syncToNetwork " + syncToNetwork); + } + + // Notify for any user other than the caller's own requires permission. + final int callingUserHandle = UserHandle.getCallingUserId(); + if (userHandle != callingUserHandle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "no permission to notify other users"); + } + + // We passed the permission check; resolve pseudouser targets as appropriate + if (userHandle < 0) { + if (userHandle == UserHandle.USER_CURRENT) { + userHandle = ActivityManager.getCurrentUser(); + } else if (userHandle != UserHandle.USER_ALL) { + throw new InvalidParameterException("Bad user handle for notifyChange: " + + userHandle); + } + } + + final int uid = Binder.getCallingUid(); + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>(); + synchronized (mRootNode) { + mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications, + userHandle, calls); + } + final int numCalls = calls.size(); + for (int i=0; i<numCalls; i++) { + ObserverCall oc = calls.get(i); + try { + oc.mObserver.onChange(oc.mSelfChange, uri); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri); + } + } catch (RemoteException ex) { + synchronized (mRootNode) { + Log.w(TAG, "Found dead observer, removing"); + IBinder binder = oc.mObserver.asBinder(); + final ArrayList<ObserverNode.ObserverEntry> list + = oc.mNode.mObservers; + int numList = list.size(); + for (int j=0; j<numList; j++) { + ObserverNode.ObserverEntry oe = list.get(j); + if (oe.observer.asBinder() == binder) { + list.remove(j); + j--; + numList--; + } + } + } + } + } + if (syncToNetwork) { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid, + uri.getAuthority()); + } + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void notifyChange(Uri uri, IContentObserver observer, + boolean observerWantsSelfNotifications, boolean syncToNetwork) { + notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork, + UserHandle.getCallingUserId()); + } + + /** + * Hide this class since it is not part of api, + * but current unittest framework requires it to be public + * @hide + * + */ + public static final class ObserverCall { + final ObserverNode mNode; + final IContentObserver mObserver; + final boolean mSelfChange; + + ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) { + mNode = node; + mObserver = observer; + mSelfChange = selfChange; + } + } + + public void requestSync(Account account, String authority, Bundle extras) { + ContentResolver.validateSyncExtrasBundle(extras); + int userId = UserHandle.getCallingUserId(); + int uId = Binder.getCallingUid(); + + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.scheduleSync(account, userId, uId, authority, extras, 0 /* no delay */, + false /* onlyThoseWithUnkownSyncableState */); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** + * Clear all scheduled sync operations that match the uri and cancel the active sync + * if they match the authority and account, if they are present. + * @param account filter the pending and active syncs to cancel using this account + * @param authority filter the pending and active syncs to cancel using this authority + */ + public void cancelSync(Account account, String authority) { + int userId = UserHandle.getCallingUserId(); + + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.clearScheduledSyncOperations(account, userId, authority); + syncManager.cancelActiveSync(account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** + * Get information about the SyncAdapters that are known to the system. + * @return an array of SyncAdapters that have registered with the system + */ + public SyncAdapterType[] getSyncAdapterTypes() { + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + final int userId = UserHandle.getCallingUserId(); + final long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + return syncManager.getSyncAdapterTypes(userId); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean getSyncAutomatically(Account account, String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getSyncAutomatically( + account, userId, providerName); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void setSyncAutomatically(Account account, String providerName, boolean sync) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setSyncAutomatically( + account, userId, providerName, sync); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void addPeriodicSync(Account account, String authority, Bundle extras, + long pollFrequency) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + getSyncManager().getSyncStorageEngine().addPeriodicSync( + account, userId, authority, extras, pollFrequency); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void removePeriodicSync(Account account, String authority, Bundle extras) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority, + extras); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( + account, userId, providerName); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public int getIsSyncable(Account account, String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getIsSyncable( + account, userId, providerName); + } + } finally { + restoreCallingIdentity(identityToken); + } + return -1; + } + + public void setIsSyncable(Account account, String providerName, int syncable) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setIsSyncable( + account, userId, providerName, syncable); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean getMasterSyncAutomatically() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void setMasterSyncAutomatically(boolean flag) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public boolean isSyncActive(Account account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().isSyncActive( + account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public List<SyncInfo> getCurrentSyncs() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public SyncStatusInfo getSyncStatus(Account account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority( + account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return null; + } + + public boolean isSyncPending(Account account, String authority) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, + "no permission to read the sync stats"); + int userId = UserHandle.getCallingUserId(); + + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority); + } + } finally { + restoreCallingIdentity(identityToken); + } + return false; + } + + public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null && callback != null) { + syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void removeStatusChangeListener(ISyncStatusObserver callback) { + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null && callback != null) { + syncManager.getSyncStorageEngine().removeStatusChangeListener(callback); + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + public static ContentService main(Context context, boolean factoryTest) { + ContentService service = new ContentService(context, factoryTest); + ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service); + return service; + } + + /** + * Hide this class since it is not part of api, + * but current unittest framework requires it to be public + * @hide + */ + public static final class ObserverNode { + private class ObserverEntry implements IBinder.DeathRecipient { + public final IContentObserver observer; + public final int uid; + public final int pid; + public final boolean notifyForDescendants; + private final int userHandle; + private final Object observersLock; + + public ObserverEntry(IContentObserver o, boolean n, Object observersLock, + int _uid, int _pid, int _userHandle) { + this.observersLock = observersLock; + observer = o; + uid = _uid; + pid = _pid; + userHandle = _userHandle; + notifyForDescendants = n; + try { + observer.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + public void binderDied() { + synchronized (observersLock) { + removeObserverLocked(observer); + } + } + + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, + String name, String prefix, SparseIntArray pidCounts) { + pidCounts.put(pid, pidCounts.get(pid)+1); + pw.print(prefix); pw.print(name); pw.print(": pid="); + pw.print(pid); pw.print(" uid="); + pw.print(uid); pw.print(" user="); + pw.print(userHandle); pw.print(" target="); + pw.println(Integer.toHexString(System.identityHashCode( + observer != null ? observer.asBinder() : null))); + } + } + + public static final int INSERT_TYPE = 0; + public static final int UPDATE_TYPE = 1; + public static final int DELETE_TYPE = 2; + + private String mName; + private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>(); + private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>(); + + public ObserverNode(String name) { + mName = name; + } + + public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, + String name, String prefix, int[] counts, SparseIntArray pidCounts) { + String innerName = null; + if (mObservers.size() > 0) { + if ("".equals(name)) { + innerName = mName; + } else { + innerName = name + "/" + mName; + } + for (int i=0; i<mObservers.size(); i++) { + counts[1]++; + mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix, + pidCounts); + } + } + if (mChildren.size() > 0) { + if (innerName == null) { + if ("".equals(name)) { + innerName = mName; + } else { + innerName = name + "/" + mName; + } + } + for (int i=0; i<mChildren.size(); i++) { + counts[0]++; + mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix, + counts, pidCounts); + } + } + } + + private String getUriSegment(Uri uri, int index) { + if (uri != null) { + if (index == 0) { + return uri.getAuthority(); + } else { + return uri.getPathSegments().get(index - 1); + } + } else { + return null; + } + } + + private int countUriSegments(Uri uri) { + if (uri == null) { + return 0; + } + return uri.getPathSegments().size() + 1; + } + + // Invariant: userHandle is either a hard user number or is USER_ALL + public void addObserverLocked(Uri uri, IContentObserver observer, + boolean notifyForDescendants, Object observersLock, + int uid, int pid, int userHandle) { + addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock, + uid, pid, userHandle); + } + + private void addObserverLocked(Uri uri, int index, IContentObserver observer, + boolean notifyForDescendants, Object observersLock, + int uid, int pid, int userHandle) { + // If this is the leaf node add the observer + if (index == countUriSegments(uri)) { + mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock, + uid, pid, userHandle)); + return; + } + + // Look to see if the proper child already exists + String segment = getUriSegment(uri, index); + if (segment == null) { + throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer"); + } + int N = mChildren.size(); + for (int i = 0; i < N; i++) { + ObserverNode node = mChildren.get(i); + if (node.mName.equals(segment)) { + node.addObserverLocked(uri, index + 1, observer, notifyForDescendants, + observersLock, uid, pid, userHandle); + return; + } + } + + // No child found, create one + ObserverNode node = new ObserverNode(segment); + mChildren.add(node); + node.addObserverLocked(uri, index + 1, observer, notifyForDescendants, + observersLock, uid, pid, userHandle); + } + + public boolean removeObserverLocked(IContentObserver observer) { + int size = mChildren.size(); + for (int i = 0; i < size; i++) { + boolean empty = mChildren.get(i).removeObserverLocked(observer); + if (empty) { + mChildren.remove(i); + i--; + size--; + } + } + + IBinder observerBinder = observer.asBinder(); + size = mObservers.size(); + for (int i = 0; i < size; i++) { + ObserverEntry entry = mObservers.get(i); + if (entry.observer.asBinder() == observerBinder) { + mObservers.remove(i); + // We no longer need to listen for death notifications. Remove it. + observerBinder.unlinkToDeath(entry, 0); + break; + } + } + + if (mChildren.size() == 0 && mObservers.size() == 0) { + return true; + } + return false; + } + + private void collectMyObserversLocked(boolean leaf, IContentObserver observer, + boolean observerWantsSelfNotifications, int targetUserHandle, + ArrayList<ObserverCall> calls) { + int N = mObservers.size(); + IBinder observerBinder = observer == null ? null : observer.asBinder(); + for (int i = 0; i < N; i++) { + ObserverEntry entry = mObservers.get(i); + + // Don't notify the observer if it sent the notification and isn't interested + // in self notifications + boolean selfChange = (entry.observer.asBinder() == observerBinder); + if (selfChange && !observerWantsSelfNotifications) { + continue; + } + + // Does this observer match the target user? + if (targetUserHandle == UserHandle.USER_ALL + || entry.userHandle == UserHandle.USER_ALL + || targetUserHandle == entry.userHandle) { + // Make sure the observer is interested in the notification + if (leaf || (!leaf && entry.notifyForDescendants)) { + calls.add(new ObserverCall(this, entry.observer, selfChange)); + } + } + } + } + + /** + * targetUserHandle is either a hard user handle or is USER_ALL + */ + public void collectObserversLocked(Uri uri, int index, IContentObserver observer, + boolean observerWantsSelfNotifications, int targetUserHandle, + ArrayList<ObserverCall> calls) { + String segment = null; + int segmentCount = countUriSegments(uri); + if (index >= segmentCount) { + // This is the leaf node, notify all observers + collectMyObserversLocked(true, observer, observerWantsSelfNotifications, + targetUserHandle, calls); + } else if (index < segmentCount){ + segment = getUriSegment(uri, index); + // Notify any observers at this level who are interested in descendants + collectMyObserversLocked(false, observer, observerWantsSelfNotifications, + targetUserHandle, calls); + } + + int N = mChildren.size(); + for (int i = 0; i < N; i++) { + ObserverNode node = mChildren.get(i); + if (segment == null || node.mName.equals(segment)) { + // We found the child, + node.collectObserversLocked(uri, index + 1, + observer, observerWantsSelfNotifications, targetUserHandle, calls); + if (segment != null) { + break; + } + } + } + } + } +} |
