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