ContentService.java revision 04e0d265e3385e9d1fbc35e43c4e8caffbbe3290
1/*
2 * Copyright (C) 2006 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;
18
19import android.accounts.Account;
20import android.database.IContentObserver;
21import android.database.sqlite.SQLiteException;
22import android.net.Uri;
23import android.os.Binder;
24import android.os.Bundle;
25import android.os.IBinder;
26import android.os.Parcel;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.os.UserId;
30import android.util.Log;
31import android.util.SparseIntArray;
32import android.Manifest;
33
34import java.io.FileDescriptor;
35import java.io.PrintWriter;
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.Comparator;
39import java.util.List;
40
41/**
42 * {@hide}
43 */
44public final class ContentService extends IContentService.Stub {
45    private static final String TAG = "ContentService";
46    private Context mContext;
47    private boolean mFactoryTest;
48    private final ObserverNode mRootNode = new ObserverNode("");
49    private SyncManager mSyncManager = null;
50    private final Object mSyncManagerLock = new Object();
51
52    private SyncManager getSyncManager() {
53        synchronized(mSyncManagerLock) {
54            try {
55                // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
56                if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
57            } catch (SQLiteException e) {
58                Log.e(TAG, "Can't create SyncManager", e);
59            }
60            return mSyncManager;
61        }
62    }
63
64    @Override
65    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
66        mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
67                "caller doesn't have the DUMP permission");
68
69        // This makes it so that future permission checks will be in the context of this
70        // process rather than the caller's process. We will restore this before returning.
71        long identityToken = clearCallingIdentity();
72        try {
73            if (mSyncManager == null) {
74                pw.println("No SyncManager created!  (Disk full?)");
75            } else {
76                mSyncManager.dump(fd, pw);
77            }
78            pw.println();
79            pw.println("Observer tree:");
80            synchronized (mRootNode) {
81                int[] counts = new int[2];
82                final SparseIntArray pidCounts = new SparseIntArray();
83                mRootNode.dumpLocked(fd, pw, args, "", "  ", counts, pidCounts);
84                pw.println();
85                ArrayList<Integer> sorted = new ArrayList<Integer>();
86                for (int i=0; i<pidCounts.size(); i++) {
87                    sorted.add(pidCounts.keyAt(i));
88                }
89                Collections.sort(sorted, new Comparator<Integer>() {
90                    @Override
91                    public int compare(Integer lhs, Integer rhs) {
92                        int lc = pidCounts.get(lhs);
93                        int rc = pidCounts.get(rhs);
94                        if (lc < rc) {
95                            return 1;
96                        } else if (lc > rc) {
97                            return -1;
98                        }
99                        return 0;
100                    }
101
102                });
103                for (int i=0; i<sorted.size(); i++) {
104                    int pid = sorted.get(i);
105                    pw.print("  pid "); pw.print(pid); pw.print(": ");
106                            pw.print(pidCounts.get(pid)); pw.println(" observers");
107                }
108                pw.println();
109                pw.print(" Total number of nodes: "); pw.println(counts[0]);
110                pw.print(" Total number of observers: "); pw.println(counts[1]);
111            }
112        } finally {
113            restoreCallingIdentity(identityToken);
114        }
115    }
116
117    @Override
118    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
119            throws RemoteException {
120        try {
121            return super.onTransact(code, data, reply, flags);
122        } catch (RuntimeException e) {
123            // The content service only throws security exceptions, so let's
124            // log all others.
125            if (!(e instanceof SecurityException)) {
126                Log.e(TAG, "Content Service Crash", e);
127            }
128            throw e;
129        }
130    }
131
132    /*package*/ ContentService(Context context, boolean factoryTest) {
133        mContext = context;
134        mFactoryTest = factoryTest;
135        getSyncManager();
136    }
137
138    public void registerContentObserver(Uri uri, boolean notifyForDescendents,
139            IContentObserver observer) {
140        if (observer == null || uri == null) {
141            throw new IllegalArgumentException("You must pass a valid uri and observer");
142        }
143        synchronized (mRootNode) {
144            mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode,
145                    Binder.getCallingUid(), Binder.getCallingPid());
146            if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
147                    " with notifyForDescendents " + notifyForDescendents);
148        }
149    }
150
151    public void unregisterContentObserver(IContentObserver observer) {
152        if (observer == null) {
153            throw new IllegalArgumentException("You must pass a valid observer");
154        }
155        synchronized (mRootNode) {
156            mRootNode.removeObserverLocked(observer);
157            if (false) Log.v(TAG, "Unregistered observer " + observer);
158        }
159    }
160
161    public void notifyChange(Uri uri, IContentObserver observer,
162            boolean observerWantsSelfNotifications, boolean syncToNetwork) {
163        if (Log.isLoggable(TAG, Log.VERBOSE)) {
164            Log.v(TAG, "Notifying update of " + uri + " from observer " + observer
165                    + ", syncToNetwork " + syncToNetwork);
166        }
167
168        int userId = UserId.getCallingUserId();
169        // This makes it so that future permission checks will be in the context of this
170        // process rather than the caller's process. We will restore this before returning.
171        long identityToken = clearCallingIdentity();
172        try {
173            ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
174            synchronized (mRootNode) {
175                mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
176                        calls);
177            }
178            final int numCalls = calls.size();
179            for (int i=0; i<numCalls; i++) {
180                ObserverCall oc = calls.get(i);
181                try {
182                    oc.mObserver.onChange(oc.mSelfChange, uri);
183                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
184                        Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
185                    }
186                } catch (RemoteException ex) {
187                    synchronized (mRootNode) {
188                        Log.w(TAG, "Found dead observer, removing");
189                        IBinder binder = oc.mObserver.asBinder();
190                        final ArrayList<ObserverNode.ObserverEntry> list
191                                = oc.mNode.mObservers;
192                        int numList = list.size();
193                        for (int j=0; j<numList; j++) {
194                            ObserverNode.ObserverEntry oe = list.get(j);
195                            if (oe.observer.asBinder() == binder) {
196                                list.remove(j);
197                                j--;
198                                numList--;
199                            }
200                        }
201                    }
202                }
203            }
204            if (syncToNetwork) {
205                SyncManager syncManager = getSyncManager();
206                if (syncManager != null) {
207                    syncManager.scheduleLocalSync(null /* all accounts */, userId,
208                            uri.getAuthority());
209                }
210            }
211        } finally {
212            restoreCallingIdentity(identityToken);
213        }
214    }
215
216    /**
217     * Hide this class since it is not part of api,
218     * but current unittest framework requires it to be public
219     * @hide
220     *
221     */
222    public static final class ObserverCall {
223        final ObserverNode mNode;
224        final IContentObserver mObserver;
225        final boolean mSelfChange;
226
227        ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) {
228            mNode = node;
229            mObserver = observer;
230            mSelfChange = selfChange;
231        }
232    }
233
234    public void requestSync(Account account, String authority, Bundle extras) {
235        ContentResolver.validateSyncExtrasBundle(extras);
236        int userId = UserId.getCallingUserId();
237
238        // This makes it so that future permission checks will be in the context of this
239        // process rather than the caller's process. We will restore this before returning.
240        long identityToken = clearCallingIdentity();
241        try {
242            SyncManager syncManager = getSyncManager();
243            if (syncManager != null) {
244                syncManager.scheduleSync(account, userId, authority, extras, 0 /* no delay */,
245                        false /* onlyThoseWithUnkownSyncableState */);
246            }
247        } finally {
248            restoreCallingIdentity(identityToken);
249        }
250    }
251
252    /**
253     * Clear all scheduled sync operations that match the uri and cancel the active sync
254     * if they match the authority and account, if they are present.
255     * @param account filter the pending and active syncs to cancel using this account
256     * @param authority filter the pending and active syncs to cancel using this authority
257     */
258    public void cancelSync(Account account, String authority) {
259        int userId = UserId.getCallingUserId();
260
261        // This makes it so that future permission checks will be in the context of this
262        // process rather than the caller's process. We will restore this before returning.
263        long identityToken = clearCallingIdentity();
264        try {
265            SyncManager syncManager = getSyncManager();
266            if (syncManager != null) {
267                syncManager.clearScheduledSyncOperations(account, userId, authority);
268                syncManager.cancelActiveSync(account, userId, authority);
269            }
270        } finally {
271            restoreCallingIdentity(identityToken);
272        }
273    }
274
275    /**
276     * Get information about the SyncAdapters that are known to the system.
277     * @return an array of SyncAdapters that have registered with the system
278     */
279    public SyncAdapterType[] getSyncAdapterTypes() {
280        // This makes it so that future permission checks will be in the context of this
281        // process rather than the caller's process. We will restore this before returning.
282        long identityToken = clearCallingIdentity();
283        try {
284            SyncManager syncManager = getSyncManager();
285            return syncManager.getSyncAdapterTypes();
286        } finally {
287            restoreCallingIdentity(identityToken);
288        }
289    }
290
291    public boolean getSyncAutomatically(Account account, String providerName) {
292        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
293                "no permission to read the sync settings");
294        int userId = UserId.getCallingUserId();
295
296        long identityToken = clearCallingIdentity();
297        try {
298            SyncManager syncManager = getSyncManager();
299            if (syncManager != null) {
300                return syncManager.getSyncStorageEngine().getSyncAutomatically(
301                        account, userId, providerName);
302            }
303        } finally {
304            restoreCallingIdentity(identityToken);
305        }
306        return false;
307    }
308
309    public void setSyncAutomatically(Account account, String providerName, boolean sync) {
310        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
311                "no permission to write the sync settings");
312        int userId = UserId.getCallingUserId();
313
314        long identityToken = clearCallingIdentity();
315        try {
316            SyncManager syncManager = getSyncManager();
317            if (syncManager != null) {
318                syncManager.getSyncStorageEngine().setSyncAutomatically(
319                        account, userId, providerName, sync);
320            }
321        } finally {
322            restoreCallingIdentity(identityToken);
323        }
324    }
325
326    public void addPeriodicSync(Account account, String authority, Bundle extras,
327            long pollFrequency) {
328        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
329                "no permission to write the sync settings");
330        int userId = UserId.getCallingUserId();
331
332        long identityToken = clearCallingIdentity();
333        try {
334            getSyncManager().getSyncStorageEngine().addPeriodicSync(
335                    account, userId, authority, extras, pollFrequency);
336        } finally {
337            restoreCallingIdentity(identityToken);
338        }
339    }
340
341    public void removePeriodicSync(Account account, String authority, Bundle extras) {
342        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
343                "no permission to write the sync settings");
344        int userId = UserId.getCallingUserId();
345
346        long identityToken = clearCallingIdentity();
347        try {
348            getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority,
349                    extras);
350        } finally {
351            restoreCallingIdentity(identityToken);
352        }
353    }
354
355    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
356        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
357                "no permission to read the sync settings");
358        int userId = UserId.getCallingUserId();
359
360        long identityToken = clearCallingIdentity();
361        try {
362            return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
363                    account, userId, providerName);
364        } finally {
365            restoreCallingIdentity(identityToken);
366        }
367    }
368
369    public int getIsSyncable(Account account, String providerName) {
370        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
371                "no permission to read the sync settings");
372        int userId = UserId.getCallingUserId();
373
374        long identityToken = clearCallingIdentity();
375        try {
376            SyncManager syncManager = getSyncManager();
377            if (syncManager != null) {
378                return syncManager.getSyncStorageEngine().getIsSyncable(
379                        account, userId, providerName);
380            }
381        } finally {
382            restoreCallingIdentity(identityToken);
383        }
384        return -1;
385    }
386
387    public void setIsSyncable(Account account, String providerName, int syncable) {
388        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
389                "no permission to write the sync settings");
390        int userId = UserId.getCallingUserId();
391
392        long identityToken = clearCallingIdentity();
393        try {
394            SyncManager syncManager = getSyncManager();
395            if (syncManager != null) {
396                syncManager.getSyncStorageEngine().setIsSyncable(
397                        account, userId, providerName, syncable);
398            }
399        } finally {
400            restoreCallingIdentity(identityToken);
401        }
402    }
403
404    public boolean getMasterSyncAutomatically() {
405        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
406                "no permission to read the sync settings");
407        int userId = UserId.getCallingUserId();
408
409        long identityToken = clearCallingIdentity();
410        try {
411            SyncManager syncManager = getSyncManager();
412            if (syncManager != null) {
413                return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
414            }
415        } finally {
416            restoreCallingIdentity(identityToken);
417        }
418        return false;
419    }
420
421    public void setMasterSyncAutomatically(boolean flag) {
422        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
423                "no permission to write the sync settings");
424        int userId = UserId.getCallingUserId();
425
426        long identityToken = clearCallingIdentity();
427        try {
428            SyncManager syncManager = getSyncManager();
429            if (syncManager != null) {
430                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
431            }
432        } finally {
433            restoreCallingIdentity(identityToken);
434        }
435    }
436
437    public boolean isSyncActive(Account account, String authority) {
438        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
439                "no permission to read the sync stats");
440        int userId = UserId.getCallingUserId();
441
442        long identityToken = clearCallingIdentity();
443        try {
444            SyncManager syncManager = getSyncManager();
445            if (syncManager != null) {
446                return syncManager.getSyncStorageEngine().isSyncActive(
447                        account, userId, authority);
448            }
449        } finally {
450            restoreCallingIdentity(identityToken);
451        }
452        return false;
453    }
454
455    public List<SyncInfo> getCurrentSyncs() {
456        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
457                "no permission to read the sync stats");
458        int userId = UserId.getCallingUserId();
459
460        long identityToken = clearCallingIdentity();
461        try {
462            return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
463        } finally {
464            restoreCallingIdentity(identityToken);
465        }
466    }
467
468    public SyncStatusInfo getSyncStatus(Account account, String authority) {
469        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
470                "no permission to read the sync stats");
471        int userId = UserId.getCallingUserId();
472
473        long identityToken = clearCallingIdentity();
474        try {
475            SyncManager syncManager = getSyncManager();
476            if (syncManager != null) {
477                return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
478                        account, userId, authority);
479            }
480        } finally {
481            restoreCallingIdentity(identityToken);
482        }
483        return null;
484    }
485
486    public boolean isSyncPending(Account account, String authority) {
487        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
488                "no permission to read the sync stats");
489        int userId = UserId.getCallingUserId();
490
491        long identityToken = clearCallingIdentity();
492        try {
493            SyncManager syncManager = getSyncManager();
494            if (syncManager != null) {
495                return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
496            }
497        } finally {
498            restoreCallingIdentity(identityToken);
499        }
500        return false;
501    }
502
503    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
504        long identityToken = clearCallingIdentity();
505        try {
506            SyncManager syncManager = getSyncManager();
507            if (syncManager != null && callback != null) {
508                syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
509            }
510        } finally {
511            restoreCallingIdentity(identityToken);
512        }
513    }
514
515    public void removeStatusChangeListener(ISyncStatusObserver callback) {
516        long identityToken = clearCallingIdentity();
517        try {
518            SyncManager syncManager = getSyncManager();
519            if (syncManager != null && callback != null) {
520                syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
521            }
522        } finally {
523            restoreCallingIdentity(identityToken);
524        }
525    }
526
527    public static IContentService main(Context context, boolean factoryTest) {
528        ContentService service = new ContentService(context, factoryTest);
529        ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
530        return service;
531    }
532
533    /**
534     * Hide this class since it is not part of api,
535     * but current unittest framework requires it to be public
536     * @hide
537     */
538    public static final class ObserverNode {
539        private class ObserverEntry implements IBinder.DeathRecipient {
540            public final IContentObserver observer;
541            public final int uid;
542            public final int pid;
543            public final boolean notifyForDescendents;
544            private final Object observersLock;
545
546            public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
547                    int _uid, int _pid) {
548                this.observersLock = observersLock;
549                observer = o;
550                uid = _uid;
551                pid = _pid;
552                notifyForDescendents = n;
553                try {
554                    observer.asBinder().linkToDeath(this, 0);
555                } catch (RemoteException e) {
556                    binderDied();
557                }
558            }
559
560            public void binderDied() {
561                synchronized (observersLock) {
562                    removeObserverLocked(observer);
563                }
564            }
565
566            public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
567                    String name, String prefix, SparseIntArray pidCounts) {
568                pidCounts.put(pid, pidCounts.get(pid)+1);
569                pw.print(prefix); pw.print(name); pw.print(": pid=");
570                        pw.print(pid); pw.print(" uid=");
571                        pw.print(uid); pw.print(" target=");
572                        pw.println(Integer.toHexString(System.identityHashCode(
573                                observer != null ? observer.asBinder() : null)));
574            }
575        }
576
577        public static final int INSERT_TYPE = 0;
578        public static final int UPDATE_TYPE = 1;
579        public static final int DELETE_TYPE = 2;
580
581        private String mName;
582        private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
583        private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
584
585        public ObserverNode(String name) {
586            mName = name;
587        }
588
589        public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
590                String name, String prefix, int[] counts, SparseIntArray pidCounts) {
591            String innerName = null;
592            if (mObservers.size() > 0) {
593                if ("".equals(name)) {
594                    innerName = mName;
595                } else {
596                    innerName = name + "/" + mName;
597                }
598                for (int i=0; i<mObservers.size(); i++) {
599                    counts[1]++;
600                    mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
601                            pidCounts);
602                }
603            }
604            if (mChildren.size() > 0) {
605                if (innerName == null) {
606                    if ("".equals(name)) {
607                        innerName = mName;
608                    } else {
609                        innerName = name + "/" + mName;
610                    }
611                }
612                for (int i=0; i<mChildren.size(); i++) {
613                    counts[0]++;
614                    mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
615                            counts, pidCounts);
616                }
617            }
618        }
619
620        private String getUriSegment(Uri uri, int index) {
621            if (uri != null) {
622                if (index == 0) {
623                    return uri.getAuthority();
624                } else {
625                    return uri.getPathSegments().get(index - 1);
626                }
627            } else {
628                return null;
629            }
630        }
631
632        private int countUriSegments(Uri uri) {
633            if (uri == null) {
634                return 0;
635            }
636            return uri.getPathSegments().size() + 1;
637        }
638
639        public void addObserverLocked(Uri uri, IContentObserver observer,
640                boolean notifyForDescendents, Object observersLock, int uid, int pid) {
641            addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock, uid, pid);
642        }
643
644        private void addObserverLocked(Uri uri, int index, IContentObserver observer,
645                boolean notifyForDescendents, Object observersLock, int uid, int pid) {
646            // If this is the leaf node add the observer
647            if (index == countUriSegments(uri)) {
648                mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock,
649                        uid, pid));
650                return;
651            }
652
653            // Look to see if the proper child already exists
654            String segment = getUriSegment(uri, index);
655            if (segment == null) {
656                throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
657            }
658            int N = mChildren.size();
659            for (int i = 0; i < N; i++) {
660                ObserverNode node = mChildren.get(i);
661                if (node.mName.equals(segment)) {
662                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
663                            observersLock, uid, pid);
664                    return;
665                }
666            }
667
668            // No child found, create one
669            ObserverNode node = new ObserverNode(segment);
670            mChildren.add(node);
671            node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
672                    observersLock, uid, pid);
673        }
674
675        public boolean removeObserverLocked(IContentObserver observer) {
676            int size = mChildren.size();
677            for (int i = 0; i < size; i++) {
678                boolean empty = mChildren.get(i).removeObserverLocked(observer);
679                if (empty) {
680                    mChildren.remove(i);
681                    i--;
682                    size--;
683                }
684            }
685
686            IBinder observerBinder = observer.asBinder();
687            size = mObservers.size();
688            for (int i = 0; i < size; i++) {
689                ObserverEntry entry = mObservers.get(i);
690                if (entry.observer.asBinder() == observerBinder) {
691                    mObservers.remove(i);
692                    // We no longer need to listen for death notifications. Remove it.
693                    observerBinder.unlinkToDeath(entry, 0);
694                    break;
695                }
696            }
697
698            if (mChildren.size() == 0 && mObservers.size() == 0) {
699                return true;
700            }
701            return false;
702        }
703
704        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
705                boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
706            int N = mObservers.size();
707            IBinder observerBinder = observer == null ? null : observer.asBinder();
708            for (int i = 0; i < N; i++) {
709                ObserverEntry entry = mObservers.get(i);
710
711                // Don't notify the observer if it sent the notification and isn't interesed
712                // in self notifications
713                boolean selfChange = (entry.observer.asBinder() == observerBinder);
714                if (selfChange && !observerWantsSelfNotifications) {
715                    continue;
716                }
717
718                // Make sure the observer is interested in the notification
719                if (leaf || (!leaf && entry.notifyForDescendents)) {
720                    calls.add(new ObserverCall(this, entry.observer, selfChange));
721                }
722            }
723        }
724
725        public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
726                boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
727            String segment = null;
728            int segmentCount = countUriSegments(uri);
729            if (index >= segmentCount) {
730                // This is the leaf node, notify all observers
731                collectMyObserversLocked(true, observer, observerWantsSelfNotifications, calls);
732            } else if (index < segmentCount){
733                segment = getUriSegment(uri, index);
734                // Notify any observers at this level who are interested in descendents
735                collectMyObserversLocked(false, observer, observerWantsSelfNotifications, calls);
736            }
737
738            int N = mChildren.size();
739            for (int i = 0; i < N; i++) {
740                ObserverNode node = mChildren.get(i);
741                if (segment == null || node.mName.equals(segment)) {
742                    // We found the child,
743                    node.collectObserversLocked(uri, index + 1,
744                            observer, observerWantsSelfNotifications, calls);
745                    if (segment != null) {
746                        break;
747                    }
748                }
749            }
750        }
751    }
752}
753