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