content: /*
   * 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.annotation.Nullable;
  import android.annotation.RequiresPermission;
  import android.app.ActivityManager;
  import android.app.ActivityManagerInternal;
  import android.app.AppGlobals;
  import android.app.AppOpsManager;
  import android.app.job.JobInfo;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.ContentResolver.SyncExemption;
  import android.content.Context;
  import android.content.IContentService;
  import android.content.ISyncStatusObserver;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.PeriodicSync;
  import android.content.SyncAdapterType;
  import android.content.SyncInfo;
  import android.content.SyncRequest;
  import android.content.SyncStatusInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManagerInternal;
  import android.content.pm.ProviderInfo;
  import android.database.IContentObserver;
  import android.database.sqlite.SQLiteException;
  import android.net.Uri;
  import android.os.Binder;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.FactoryTest;
  import android.os.IBinder;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.ResultReceiver;
  import android.os.ShellCallback;
  import android.os.UserHandle;
  import android.text.TextUtils;
  import android.util.ArrayMap;
  import android.util.ArraySet;
  import android.util.Log;
  import android.util.Pair;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.util.SparseIntArray;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.os.BinderDeathDispatcher;
  import com.android.internal.util.ArrayUtils;
  import com.android.internal.util.DumpUtils;
  import com.android.internal.util.IndentingPrintWriter;
  import com.android.server.LocalServices;
  import com.android.server.SystemService;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  
  /**
   * {@hide}
   */
  public final class ContentService extends IContentService.Stub {
      static final String TAG = "ContentService";
      static final boolean DEBUG = false;
  
      /** Do a WTF if a single observer is registered more than this times. */
      private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000;
  
      public static class Lifecycle extends SystemService {
          private ContentService mService;
  
          public Lifecycle(Context context) {
              super(context);
          }
  
          @Override
          public void onStart() {
              final boolean factoryTest = (FactoryTest
                      .getMode() == FactoryTest.FACTORY_TEST_LOW_LEVEL);
              mService = new ContentService(getContext(), factoryTest);
              publishBinderService(ContentResolver.CONTENT_SERVICE_NAME, mService);
          }
  
          @Override
          public void onBootPhase(int phase) {
              mService.onBootPhase(phase);
          }
  
  
          @Override
          public void onStartUser(int userHandle) {
              mService.onStartUser(userHandle);
          }
  
          @Override
          public void onUnlockUser(int userHandle) {
              mService.onUnlockUser(userHandle);
          }
  
          @Override
          public void onStopUser(int userHandle) {
              mService.onStopUser(userHandle);
          }
  
          @Override
          public void onCleanupUser(int userHandle) {
              synchronized (mService.mCache) {
                  mService.mCache.remove(userHandle);
              }
          }
      }
  
      private Context mContext;
      private boolean mFactoryTest;
  
      private final ObserverNode mRootNode = new ObserverNode("");
  
      private SyncManager mSyncManager = null;
      private final Object mSyncManagerLock = new Object();
  
      private static final BinderDeathDispatcher<IContentObserver> sObserverDeathDispatcher =
              new BinderDeathDispatcher<>();
  
      @GuardedBy("sObserverLeakDetectedUid")
      private static final ArraySet<Integer> sObserverLeakDetectedUid = new ArraySet<>(0);
  
      /**
       * Map from userId to providerPackageName to [clientPackageName, uri] to
       * value. This structure is carefully optimized to keep invalidation logic
       * as cheap as possible.
       */
      @GuardedBy("mCache")
      private final SparseArray<ArrayMap<String, ArrayMap<Pair<String, Uri>, Bundle>>>
              mCache = new SparseArray<>();
  
      private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              synchronized (mCache) {
                  if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
                      mCache.clear();
                  } else {
                      final Uri data = intent.getData();
                      if (data != null) {
                          final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                                  UserHandle.USER_NULL);
                          final String packageName = data.getSchemeSpecificPart();
                          invalidateCacheLocked(userId, packageName, null);
                      }
                  }
              }
          }
      };
  
      private SyncManager getSyncManager() {
          synchronized(mSyncManagerLock) {
              try {
                  // Try to create the SyncManager, return null if it fails (which it shouldn't).
                  if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
              } catch (SQLiteException e) {
                  Log.e(TAG, "Can't create SyncManager", e);
              }
              return mSyncManager;
          }
      }
  
      void onStartUser(int userHandle) {
          if (mSyncManager != null) mSyncManager.onStartUser(userHandle);
      }
  
      void onUnlockUser(int userHandle) {
          if (mSyncManager != null) mSyncManager.onUnlockUser(userHandle);
      }
  
      void onStopUser(int userHandle) {
          if (mSyncManager != null) mSyncManager.onStopUser(userHandle);
      }
  
      @Override
      protected synchronized void dump(FileDescriptor fd, PrintWriter pw_, String[] args) {
          if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw_)) return;
          final IndentingPrintWriter pw = new IndentingPrintWriter(pw_, "  ");
  
          final boolean dumpAll = ArrayUtils.contains(args, "-a");
  
          // 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 long identityToken = clearCallingIdentity();
          try {
              if (mSyncManager == null) {
                  pw.println("SyncManager not available yet");
              } else {
                  mSyncManager.dump(fd, pw, dumpAll);
              }
              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]);
  
                  sObserverDeathDispatcher.dump(pw, " ");
              }
              synchronized (sObserverLeakDetectedUid) {
                  pw.println();
                  pw.print("Observer leaking UIDs: ");
                  pw.println(sObserverLeakDetectedUid.toString());
              }
  
              synchronized (mCache) {
                  pw.println();
                  pw.println("Cached content:");
                  pw.increaseIndent();
                  for (int i = 0; i < mCache.size(); i++) {
                      pw.println("User " + mCache.keyAt(i) + ":");
                      pw.increaseIndent();
                      pw.println(mCache.valueAt(i));
                      pw.decreaseIndent();
                  }
                  pw.decreaseIndent();
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      /*package*/ ContentService(Context context, boolean factoryTest) {
          mContext = context;
          mFactoryTest = factoryTest;
  
          // Let the package manager query for the sync adapters for a given authority
          // as we grant default permissions to sync adapters for specific authorities.
          PackageManagerInternal packageManagerInternal = LocalServices.getService(
                  PackageManagerInternal.class);
          packageManagerInternal.setSyncAdapterPackagesprovider(
                  new PackageManagerInternal.SyncAdapterPackagesProvider() {
                      @Override
                      public String[] getPackages(String authority, int userId) {
                          return getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
                      }
                  });
  
          final IntentFilter packageFilter = new IntentFilter();
          packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
          packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
          packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
          packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
          packageFilter.addDataScheme("package");
          mContext.registerReceiverAsUser(mCacheReceiver, UserHandle.ALL,
                  packageFilter, null, null);
  
          final IntentFilter localeFilter = new IntentFilter();
          localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
          mContext.registerReceiverAsUser(mCacheReceiver, UserHandle.ALL,
                  localeFilter, null, null);
      }
  
      void onBootPhase(int phase) {
          switch (phase) {
              case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                  getSyncManager();
                  break;
          }
          if (mSyncManager != null) {
              mSyncManager.onBootPhase(phase);
          }
      }
  
      /**
       * 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 or hold a read uri grant to the uri.
       *     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, int targetSdkVersion) {
          if (observer == null || uri == null) {
              throw new IllegalArgumentException("You must pass a valid uri and observer");
          }
  
          final int uid = Binder.getCallingUid();
          final int pid = Binder.getCallingPid();
  
          userHandle = handleIncomingUser(uri, pid, uid,
                  Intent.FLAG_GRANT_READ_URI_PERMISSION, true, userHandle);
  
          final String msg = LocalServices.getService(ActivityManagerInternal.class)
                  .checkContentProviderAccess(uri.getAuthority(), userHandle);
          if (msg != null) {
              if (targetSdkVersion >= Build.VERSION_CODES.O) {
                  throw new SecurityException(msg);
              } else {
                  if (msg.startsWith("Failed to find provider")) {
                      // Sigh, we need to quietly let apps targeting older API
                      // levels notify on non-existent providers.
                  } else {
                      Log.w(TAG, "Ignoring content changes for " + uri + " from " + uid + ": " + msg);
                      return;
                  }
              }
          }
  
          synchronized (mRootNode) {
              mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
                      uid, pid, 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(), Build.VERSION_CODES.CUR_DEVELOPMENT);
      }
  
      @Override
      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 or hold a write uri grant to the uri.
       *     Pseudousers USER_ALL and USER_CURRENT are properly interpreted; no other pseudousers are
       *     allowed.
       */
      @Override
      public void notifyChange(Uri uri, IContentObserver observer,
              boolean observerWantsSelfNotifications, int flags, int userHandle,
              int targetSdkVersion, String callingPackage) {
          if (DEBUG) Slog.d(TAG, "Notifying update of " + uri + " for user " + userHandle
                  + " from observer " + observer + ", flags " + Integer.toHexString(flags));
  
          if (uri == null) {
              throw new NullPointerException("Uri must not be null");
          }
  
          final int callingUid = Binder.getCallingUid();
          final int callingPid = Binder.getCallingPid();
          final int callingUserHandle = UserHandle.getCallingUserId();
  
          userHandle = handleIncomingUser(uri, callingPid, callingUid,
                  Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true, userHandle);
  
          final String msg = LocalServices.getService(ActivityManagerInternal.class)
                  .checkContentProviderAccess(uri.getAuthority(), userHandle);
          if (msg != null) {
              if (targetSdkVersion >= Build.VERSION_CODES.O) {
                  throw new SecurityException(msg);
              } else {
                  if (msg.startsWith("Failed to find provider")) {
                      // Sigh, we need to quietly let apps targeting older API
                      // levels notify on non-existent providers.
                  } else {
                      Log.w(TAG, "Ignoring notify for " + uri + " from " + callingUid + ": " + msg);
                      return;
                  }
              }
          }
  
          // 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,
                          flags, 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, userHandle);
                      if (DEBUG) Slog.d(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 ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
                  SyncManager syncManager = getSyncManager();
                  if (syncManager != null) {
                      syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle,
                              callingUid,
                              uri.getAuthority(), getSyncExemptionForCaller(callingUid),
                              callingUid, callingPid, callingPackage);
                  }
              }
  
              synchronized (mCache) {
                  final String providerPackageName = getProviderPackageName(uri);
                  invalidateCacheLocked(userHandle, providerPackageName, uri);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      private int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, int userHandle) {
          try {
              return ActivityManager.getService().checkUriPermission(
                      uri, pid, uid, modeFlags, userHandle, null);
          } catch (RemoteException e) {
              return PackageManager.PERMISSION_DENIED;
          }
      }
  
      public void notifyChange(Uri uri, IContentObserver observer,
              boolean observerWantsSelfNotifications, boolean syncToNetwork,
              String callingPackage) {
          notifyChange(uri, observer, observerWantsSelfNotifications,
                  syncToNetwork ? ContentResolver.NOTIFY_SYNC_TO_NETWORK : 0,
                  UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT, callingPackage);
      }
  
      /**
       * 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;
          final int mObserverUserId;
  
          ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange, int observerUserId) {
              mNode = node;
              mObserver = observer;
              mSelfChange = selfChange;
              mObserverUserId = observerUserId;
          }
      }
  
      @Override
      public void requestSync(Account account, String authority, Bundle extras,
              String callingPackage) {
          Bundle.setDefusable(extras, true);
          ContentResolver.validateSyncExtrasBundle(extras);
          int userId = UserHandle.getCallingUserId();
          final int callingUid = Binder.getCallingUid();
          final int callingPid = Binder.getCallingPid();
  
          validateExtras(callingUid, extras);
          final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(callingUid, extras);
  
          // 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, callingUid, authority, extras,
                          SyncStorageEngine.AuthorityInfo.UNDEFINED,
                          syncExemption, callingUid, callingPid, callingPackage);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      /**
       * Request a sync with a generic {@link android.content.SyncRequest} object. This will be
       * either:
       *   periodic OR one-off sync.
       * and
       *   anonymous OR provider sync.
       * Depending on the request, we enqueue to suit in the SyncManager.
       * @param request The request object. Validation of this object is done by its builder.
       */
      @Override
      public void sync(SyncRequest request, String callingPackage) {
          syncAsUser(request, UserHandle.getCallingUserId(), callingPackage);
      }
  
      private long clampPeriod(long period) {
          long minPeriod = JobInfo.getMinPeriodMillis() / 1000;
          if (period < minPeriod) {
              Slog.w(TAG, "Requested poll frequency of " + period
                      + " seconds being rounded up to " + minPeriod + "s.");
              period = minPeriod;
          }
          return period;
      }
  
      /**
       * If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       */
      @Override
      public void syncAsUser(SyncRequest request, int userId, String callingPackage) {
          enforceCrossUserPermission(userId, "no permission to request sync as user: " + userId);
          final int callingUid = Binder.getCallingUid();
          final int callingPid = Binder.getCallingPid();
  
          final Bundle extras = request.getBundle();
  
          validateExtras(callingUid, extras);
          final int syncExemption = getSyncExemptionAndCleanUpExtrasForCaller(callingUid, extras);
  
          // 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) {
                  return;
              }
              long flextime = request.getSyncFlexTime();
              long runAtTime = request.getSyncRunTime();
              if (request.isPeriodic()) {
                  mContext.enforceCallingOrSelfPermission(
                          Manifest.permission.WRITE_SYNC_SETTINGS,
                          "no permission to write the sync settings");
                  SyncStorageEngine.EndPoint info;
                  info = new SyncStorageEngine.EndPoint(
                          request.getAccount(), request.getProvider(), userId);
  
                  runAtTime = clampPeriod(runAtTime);
                  // Schedule periodic sync.
                  getSyncManager().updateOrAddPeriodicSync(info, runAtTime,
                          flextime, extras);
              } else {
                  syncManager.scheduleSync(
                          request.getAccount(), userId, callingUid, request.getProvider(), extras,
                          SyncStorageEngine.AuthorityInfo.UNDEFINED,
                          syncExemption, callingUid, callingPid, callingPackage);
              }
          } 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, or null.
       * @param authority filter the pending and active syncs to cancel using this authority, or
       * null.
       * @param cname cancel syncs running on this service, or null for provider/account.
       */
      @Override
      public void cancelSync(Account account, String authority, ComponentName cname) {
          cancelSyncAsUser(account, authority, cname, UserHandle.getCallingUserId());
      }
  
      /**
       * 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.
       *
       * <p> If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       *
       * @param account filter the pending and active syncs to cancel using this account, or null.
       * @param authority filter the pending and active syncs to cancel using this authority, or
       * null.
       * @param userId the user id for which to cancel sync operations.
       * @param cname cancel syncs running on this service, or null for provider/account.
       */
      @Override
      public void cancelSyncAsUser(Account account, String authority, ComponentName cname,
                                   int userId) {
          if (authority != null && authority.length() == 0) {
              throw new IllegalArgumentException("Authority must be non-empty");
          }
          enforceCrossUserPermission(userId,
                  "no permission to modify the sync settings for user " + userId);
          // 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();
          if (cname != null) {
              Slog.e(TAG, "cname not null.");
              return;
          }
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null) {
                  SyncStorageEngine.EndPoint info;
                  info = new SyncStorageEngine.EndPoint(account, authority, userId);
                  syncManager.clearScheduledSyncOperations(info);
                  syncManager.cancelActiveSync(info, null /* all syncs for this adapter */, "API");
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public void cancelRequest(SyncRequest request) {
          SyncManager syncManager = getSyncManager();
          if (syncManager == null) return;
          int userId = UserHandle.getCallingUserId();
          final int callingUid = Binder.getCallingUid();
  
          if (request.isPeriodic()) {
              mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                      "no permission to write the sync settings");
          }
  
          Bundle extras = new Bundle(request.getBundle());
          validateExtras(callingUid, extras);
  
          long identityToken = clearCallingIdentity();
          try {
              SyncStorageEngine.EndPoint info;
  
              Account account = request.getAccount();
              String provider = request.getProvider();
              info = new SyncStorageEngine.EndPoint(account, provider, userId);
              if (request.isPeriodic()) {
                  // Remove periodic sync.
                  getSyncManager().removePeriodicSync(info, extras,
                          "cancelRequest() by uid=" + callingUid);
              }
              // Cancel active syncs and clear pending syncs from the queue.
              syncManager.cancelScheduledSyncOperation(info, extras);
              syncManager.cancelActiveSync(info, extras, "API");
          } 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
       */
      @Override
      public SyncAdapterType[] getSyncAdapterTypes() {
          return getSyncAdapterTypesAsUser(UserHandle.getCallingUserId());
      }
  
      /**
       * Get information about the SyncAdapters that are known to the system for a particular user.
       *
       * <p> If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       *
       * @return an array of SyncAdapters that have registered with the system
       */
      @Override
      public SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
          enforceCrossUserPermission(userId,
                  "no permission to read sync settings for user " + userId);
          // 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 long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              return syncManager.getSyncAdapterTypes(userId);
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public String[] getSyncAdapterPackagesForAuthorityAsUser(String authority, int userId) {
          enforceCrossUserPermission(userId,
                  "no permission to read sync settings for user " + userId);
          // 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 long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              return syncManager.getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public boolean getSyncAutomatically(Account account, String providerName) {
          return getSyncAutomaticallyAsUser(account, providerName, UserHandle.getCallingUserId());
      }
  
      /**
       * If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       */
      @Override
      public boolean getSyncAutomaticallyAsUser(Account account, String providerName, int userId) {
          enforceCrossUserPermission(userId,
                  "no permission to read the sync settings for user " + userId);
          mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                  "no permission to read the sync settings");
  
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null) {
                  return syncManager.getSyncStorageEngine()
                          .getSyncAutomatically(account, userId, providerName);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
          return false;
      }
  
      @Override
      public void setSyncAutomatically(Account account, String providerName, boolean sync) {
          setSyncAutomaticallyAsUser(account, providerName, sync, UserHandle.getCallingUserId());
      }
  
      @Override
      public void setSyncAutomaticallyAsUser(Account account, String providerName, boolean sync,
                                             int userId) {
          if (TextUtils.isEmpty(providerName)) {
              throw new IllegalArgumentException("Authority must be non-empty");
          }
          mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                  "no permission to write the sync settings");
          enforceCrossUserPermission(userId,
                  "no permission to modify the sync settings for user " + userId);
          final int callingUid = Binder.getCallingUid();
          final int callingPid = Binder.getCallingPid();
          final int syncExemptionFlag = getSyncExemptionForCaller(callingUid);
  
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null) {
                  syncManager.getSyncStorageEngine().setSyncAutomatically(account, userId,
                          providerName, sync, syncExemptionFlag, callingUid, callingPid);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      /** Old API. Schedule periodic sync with default flexMillis time. */
      @Override
      public void addPeriodicSync(Account account, String authority, Bundle extras,
                                  long pollFrequency) {
          Bundle.setDefusable(extras, true);
          if (account == null) {
              throw new IllegalArgumentException("Account must not be null");
          }
          if (TextUtils.isEmpty(authority)) {
              throw new IllegalArgumentException("Authority must not be empty.");
          }
          mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                  "no permission to write the sync settings");
  
          validateExtras(Binder.getCallingUid(), extras);
  
          int userId = UserHandle.getCallingUserId();
  
          pollFrequency = clampPeriod(pollFrequency);
          long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
  
          long identityToken = clearCallingIdentity();
          try {
              SyncStorageEngine.EndPoint info =
                      new SyncStorageEngine.EndPoint(account, authority, userId);
              getSyncManager().updateOrAddPeriodicSync(info, pollFrequency,
                      defaultFlex, extras);
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public void removePeriodicSync(Account account, String authority, Bundle extras) {
          Bundle.setDefusable(extras, true);
          if (account == null) {
              throw new IllegalArgumentException("Account must not be null");
          }
          if (TextUtils.isEmpty(authority)) {
              throw new IllegalArgumentException("Authority must not be empty");
          }
          mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                  "no permission to write the sync settings");
  
          validateExtras(Binder.getCallingUid(), extras);
  
          final int callingUid = Binder.getCallingUid();
  
          int userId = UserHandle.getCallingUserId();
          long identityToken = clearCallingIdentity();
          try {
              getSyncManager()
                      .removePeriodicSync(
                              new SyncStorageEngine.EndPoint(account, authority, userId),
                              extras, "removePeriodicSync() by uid=" + callingUid);
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName,
                                                 ComponentName cname) {
          if (account == null) {
              throw new IllegalArgumentException("Account must not be null");
          }
          if (TextUtils.isEmpty(providerName)) {
              throw new IllegalArgumentException("Authority must not be empty");
          }
          mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                  "no permission to read the sync settings");
  
          int userId = UserHandle.getCallingUserId();
          long identityToken = clearCallingIdentity();
          try {
              return getSyncManager().getPeriodicSyncs(
                      new SyncStorageEngine.EndPoint(account, providerName, userId));
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public int getIsSyncable(Account account, String providerName) {
          return getIsSyncableAsUser(account, providerName, UserHandle.getCallingUserId());
      }
  
      /**
       * If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       */
      @Override
      public int getIsSyncableAsUser(Account account, String providerName, int userId) {
          enforceCrossUserPermission(userId,
                  "no permission to read the sync settings for user " + userId);
          mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                  "no permission to read the sync settings");
  
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null) {
                  return syncManager.computeSyncable(
                          account, userId, providerName, false);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
          return -1;
      }
  
      @Override
      public void setIsSyncable(Account account, String providerName, int syncable) {
          setIsSyncableAsUser(account, providerName, syncable, UserHandle.getCallingUserId());
      }
  
      /**
       * @hide
       */
      @Override
      public void setIsSyncableAsUser(Account account, String providerName, int syncable,
              int userId) {
          if (TextUtils.isEmpty(providerName)) {
              throw new IllegalArgumentException("Authority must not be empty");
          }
          enforceCrossUserPermission(userId,
                  "no permission to set the sync settings for user " + userId);
          mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                  "no permission to write the sync settings");
  
          syncable = normalizeSyncable(syncable);
          final int callingUid = Binder.getCallingUid();
          final int callingPid = Binder.getCallingPid();
  
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null) {
                  syncManager.getSyncStorageEngine().setIsSyncable(
                          account, userId, providerName, syncable, callingUid, callingPid);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public boolean getMasterSyncAutomatically() {
          return getMasterSyncAutomaticallyAsUser(UserHandle.getCallingUserId());
      }
  
      /**
       * If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       */
      @Override
      public boolean getMasterSyncAutomaticallyAsUser(int userId) {
          enforceCrossUserPermission(userId,
                  "no permission to read the sync settings for user " + userId);
          mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                  "no permission to read the sync settings");
  
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null) {
                  return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
          return false;
      }
  
      @Override
      public void setMasterSyncAutomatically(boolean flag) {
          setMasterSyncAutomaticallyAsUser(flag, UserHandle.getCallingUserId());
      }
  
      @Override
      public void setMasterSyncAutomaticallyAsUser(boolean flag, int userId) {
          enforceCrossUserPermission(userId,
                  "no permission to set the sync status for user " + userId);
          mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                  "no permission to write the sync settings");
  
          final int callingUid = Binder.getCallingUid();
          final int callingPid = Binder.getCallingPid();
  
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null) {
                  syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId,
                          getSyncExemptionForCaller(callingUid), callingUid, callingPid);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public boolean isSyncActive(Account account, String authority, ComponentName cname) {
          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 false;
              }
              return syncManager.getSyncStorageEngine().isSyncActive(
                      new SyncStorageEngine.EndPoint(account, authority, userId));
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public List<SyncInfo> getCurrentSyncs() {
          return getCurrentSyncsAsUser(UserHandle.getCallingUserId());
      }
  
      /**
       * If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       */
      @Override
      public List<SyncInfo> getCurrentSyncsAsUser(int userId) {
          enforceCrossUserPermission(userId,
                  "no permission to read the sync settings for user " + userId);
          mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                  "no permission to read the sync stats");
  
          final boolean canAccessAccounts =
              mContext.checkCallingOrSelfPermission(Manifest.permission.GET_ACCOUNTS)
                  == PackageManager.PERMISSION_GRANTED;
          long identityToken = clearCallingIdentity();
          try {
              return getSyncManager().getSyncStorageEngine()
                  .getCurrentSyncsCopy(userId, canAccessAccounts);
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
          return getSyncStatusAsUser(account, authority, cname, UserHandle.getCallingUserId());
      }
  
      /**
       * If the user id supplied is different to the calling user, the caller must hold the
       * INTERACT_ACROSS_USERS_FULL permission.
       */
      @Override
      public SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
                                                ComponentName cname, int userId) {
          if (TextUtils.isEmpty(authority)) {
              throw new IllegalArgumentException("Authority must not be empty");
          }
  
          enforceCrossUserPermission(userId,
                  "no permission to read the sync stats for user " + userId);
          mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                  "no permission to read the sync stats");
  
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager == null) {
                  return null;
              }
              SyncStorageEngine.EndPoint info;
              if (!(account == null || authority == null)) {
                  info = new SyncStorageEngine.EndPoint(account, authority, userId);
              } else {
                  throw new IllegalArgumentException("Must call sync status with valid authority");
              }
              return syncManager.getSyncStorageEngine().getStatusByAuthority(info);
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public boolean isSyncPending(Account account, String authority, ComponentName cname) {
          return isSyncPendingAsUser(account, authority, cname, UserHandle.getCallingUserId());
      }
  
      @Override
      public boolean isSyncPendingAsUser(Account account, String authority, ComponentName cname,
                                         int userId) {
          mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                  "no permission to read the sync stats");
          enforceCrossUserPermission(userId,
                  "no permission to retrieve the sync settings for user " + userId);
          long identityToken = clearCallingIdentity();
          SyncManager syncManager = getSyncManager();
          if (syncManager == null) return false;
  
          try {
              SyncStorageEngine.EndPoint info;
              if (!(account == null || authority == null)) {
                  info = new SyncStorageEngine.EndPoint(account, authority, userId);
              } else {
                  throw new IllegalArgumentException("Invalid authority specified");
              }
              return syncManager.getSyncStorageEngine().isSyncPending(info);
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
          final int callingUid = Binder.getCallingUid();
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null && callback != null) {
                  syncManager.getSyncStorageEngine().addStatusChangeListener(
                          mask, UserHandle.getUserId(callingUid), callback);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      @Override
      public void removeStatusChangeListener(ISyncStatusObserver callback) {
          long identityToken = clearCallingIdentity();
          try {
              SyncManager syncManager = getSyncManager();
              if (syncManager != null && callback != null) {
                  syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
              }
          } finally {
              restoreCallingIdentity(identityToken);
          }
      }
  
      private @Nullable String getProviderPackageName(Uri uri) {
          final ProviderInfo pi = mContext.getPackageManager()
                  .resolveContentProvider(uri.getAuthority(), 0);
          return (pi != null) ? pi.packageName : null;
      }
  
      @GuardedBy("mCache")
      private ArrayMap<Pair<String, Uri>, Bundle> findOrCreateCacheLocked(int userId,
              String providerPackageName) {
          ArrayMap<String, ArrayMap<Pair<String, Uri>, Bundle>> userCache = mCache.get(userId);
          if (userCache == null) {
              userCache = new ArrayMap<>();
              mCache.put(userId, userCache);
          }
          ArrayMap<Pair<String, Uri>, Bundle> packageCache = userCache.get(providerPackageName);
          if (packageCache == null) {
              packageCache = new ArrayMap<>();
              userCache.put(providerPackageName, packageCache);
          }
          return packageCache;
      }
  
      @GuardedBy("mCache")
      private void invalidateCacheLocked(int userId, String providerPackageName, Uri uri) {
          ArrayMap<String, ArrayMap<Pair<String, Uri>, Bundle>> userCache = mCache.get(userId);
          if (userCache == null) return;
  
          ArrayMap<Pair<String, Uri>, Bundle> packageCache = userCache.get(providerPackageName);
          if (packageCache == null) return;
  
          if (uri != null) {
              for (int i = 0; i < packageCache.size();) {
                  final Pair<String, Uri> key = packageCache.keyAt(i);
                  if (key.second != null && key.second.toString().startsWith(uri.toString())) {
                      if (DEBUG) Slog.d(TAG, "Invalidating cache for key " + key);
                      packageCache.removeAt(i);
                  } else {
                      i++;
                  }
              }
          } else {
              if (DEBUG) Slog.d(TAG, "Invalidating cache for package " + providerPackageName);
              packageCache.clear();
          }
      }
  
      @Override
      @RequiresPermission(android.Manifest.permission.CACHE_CONTENT)
      public void putCache(String packageName, Uri key, Bundle value, int userId) {
          Bundle.setDefusable(value, true);
          enforceCrossUserPermission(userId, TAG);
          mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CACHE_CONTENT, TAG);
          mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
                  packageName);
  
          final String providerPackageName = getProviderPackageName(key);
          final Pair<String, Uri> fullKey = Pair.create(packageName, key);
  
          synchronized (mCache) {
              final ArrayMap<Pair<String, Uri>, Bundle> cache = findOrCreateCacheLocked(userId,
                      providerPackageName);
              if (value != null) {
                  cache.put(fullKey, value);
              } else {
                  cache.remove(fullKey);
              }
          }
      }
  
      @Override
      @RequiresPermission(android.Manifest.permission.CACHE_CONTENT)
      public Bundle getCache(String packageName, Uri key, int userId) {
          enforceCrossUserPermission(userId, TAG);
          mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CACHE_CONTENT, TAG);
          mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
                  packageName);
  
          final String providerPackageName = getProviderPackageName(key);
          final Pair<String, Uri> fullKey = Pair.create(packageName, key);
  
          synchronized (mCache) {
              final ArrayMap<Pair<String, Uri>, Bundle> cache = findOrCreateCacheLocked(userId,
                      providerPackageName);
              return cache.get(fullKey);
          }
      }
  
      private int handleIncomingUser(Uri uri, int pid, int uid, int modeFlags, boolean allowNonFull,
              int userId) {
          if (userId == UserHandle.USER_CURRENT) {
              userId = ActivityManager.getCurrentUser();
          }
  
          if (userId == UserHandle.USER_ALL) {
              mContext.enforceCallingOrSelfPermission(
                      Manifest.permission.INTERACT_ACROSS_USERS_FULL, "No access to " + uri);
          } else if (userId < 0) {
              throw new IllegalArgumentException("Invalid user: " + userId);
          } else if (userId != UserHandle.getCallingUserId()) {
              if (checkUriPermission(uri, pid, uid, modeFlags,
                      userId) != PackageManager.PERMISSION_GRANTED) {
                  boolean allow = false;
                  if (mContext.checkCallingOrSelfPermission(
                          Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                                  == PackageManager.PERMISSION_GRANTED) {
                      allow = true;
                  } else if (allowNonFull && mContext.checkCallingOrSelfPermission(
                          Manifest.permission.INTERACT_ACROSS_USERS)
                                  == PackageManager.PERMISSION_GRANTED) {
                      allow = true;
                  }
                  if (!allow) {
                      final String permissions = allowNonFull
                              ? (Manifest.permission.INTERACT_ACROSS_USERS_FULL + " or " +
                                      Manifest.permission.INTERACT_ACROSS_USERS)
                              : Manifest.permission.INTERACT_ACROSS_USERS_FULL;
                      throw new SecurityException("No access to " + uri + ": neither user " + uid
                              + " nor current process has " + permissions);
                  }
              }
          }
  
          return userId;
      }
  
      /**
       * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS_FULL
       * permission, if the userHandle is not for the caller.
       *
       * @param userHandle the user handle of the user we want to act on behalf of.
       * @param message the message to log on security exception.
       */
      private void enforceCrossUserPermission(int userHandle, String message) {
          final int callingUser = UserHandle.getCallingUserId();
          if (callingUser != userHandle) {
              mContext.enforceCallingOrSelfPermission(
                      Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
          }
      }
  
      private static int normalizeSyncable(int syncable) {
          if (syncable > 0) {
              return SyncStorageEngine.AuthorityInfo.SYNCABLE;
          } else if (syncable == 0) {
              return SyncStorageEngine.AuthorityInfo.NOT_SYNCABLE;
          }
          return SyncStorageEngine.AuthorityInfo.UNDEFINED;
      }
  
      private void validateExtras(int callingUid, Bundle extras) {
          if (extras.containsKey(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG)) {
              switch (callingUid) {
                  case Process.ROOT_UID:
                  case Process.SHELL_UID:
                  case Process.SYSTEM_UID:
                      break; // Okay
                  default:
                      final String msg = "Invalid extras specified.";
                      Log.w(TAG, msg + " requestsync -f/-F needs to run on 'adb shell'");
                      throw new SecurityException(msg);
              }
          }
      }
  
      @SyncExemption
      private int getSyncExemptionForCaller(int callingUid) {
          return getSyncExemptionAndCleanUpExtrasForCaller(callingUid, null);
      }
  
      @SyncExemption
      private int getSyncExemptionAndCleanUpExtrasForCaller(int callingUid, Bundle extras) {
          if (extras != null) {
              final int exemption =
                      extras.getInt(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG, -1);
  
              // Need to remove the virtual extra.
              extras.remove(ContentResolver.SYNC_VIRTUAL_EXTRAS_EXEMPTION_FLAG);
              if (exemption != -1) {
                  return exemption;
              }
          }
          final ActivityManagerInternal ami =
                  LocalServices.getService(ActivityManagerInternal.class);
          if (ami == null) {
              return ContentResolver.SYNC_EXEMPTION_NONE;
          }
          final int procState = ami.getUidProcessState(callingUid);
          final boolean isUidActive = ami.isUidActive(callingUid);
  
          // Providers bound by a TOP app will get PROCESS_STATE_BOUND_TOP, so include those as well
          if (procState <= ActivityManager.PROCESS_STATE_TOP
                  || procState == ActivityManager.PROCESS_STATE_BOUND_TOP) {
              return ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP;
          }
          if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || isUidActive) {
              return ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET;
          }
          return ContentResolver.SYNC_EXEMPTION_NONE;
      }
  
      /**
       * 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, Uri uri) {
                  this.observersLock = observersLock;
                  observer = o;
                  uid = _uid;
                  pid = _pid;
                  userHandle = _userHandle;
                  notifyForDescendants = n;
  
                  final int entries = sObserverDeathDispatcher.linkToDeath(observer, this);
                  if (entries == -1) {
                      binderDied();
                  } else if (entries == TOO_MANY_OBSERVERS_THRESHOLD) {
                      boolean alreadyDetected;
  
                      synchronized (sObserverLeakDetectedUid) {
                          alreadyDetected = sObserverLeakDetectedUid.contains(uid);
                          if (!alreadyDetected) {
                              sObserverLeakDetectedUid.add(uid);
                          }
                      }
                      if (!alreadyDetected) {
                          String caller = null;
                          try {
                              caller = ArrayUtils.firstOrNull(AppGlobals.getPackageManager()
                                      .getPackagesForUid(uid));
                          } catch (RemoteException ignore) {
                          }
                          Slog.wtf(TAG, "Observer registered too many times. Leak? cpid=" + pid
                                  + " cuid=" + uid
                                  + " cpkg=" + caller
                                  + " url=" + uri);
                      }
                  }
  
              }
  
              @Override
              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, uri));
                  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.
                      sObserverDeathDispatcher.unlinkToDeath(observer, entry);
                      break;
                  }
              }
  
              if (mChildren.size() == 0 && mObservers.size() == 0) {
                  return true;
              }
              return false;
          }
  
          private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
                                                boolean observerWantsSelfNotifications, int flags,
                                                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) {
                          // If we are at the leaf: we always report, unless the sender has asked
                          // to skip observers that are notifying for descendants (since they will
                          // be sending another more specific URI for them).
                          if ((flags&ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS) != 0
                                  && entry.notifyForDescendants) {
                              if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
                                      + ": skip notify for descendants");
                              continue;
                          }
                      } else {
                          // If we are not at the leaf: we report if the observer says it wants
                          // to be notified for all descendants.
                          if (!entry.notifyForDescendants) {
                              if (DEBUG) Slog.d(TAG, "Skipping " + entry.observer
                                      + ": not monitor descendants");
                              continue;
                          }
                      }
                      if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
                              + " flags=" + Integer.toHexString(flags)
                              + " desc=" + entry.notifyForDescendants);
                      calls.add(new ObserverCall(this, entry.observer, selfChange,
                              UserHandle.getUserId(entry.uid)));
                  }
              }
          }
  
          /**
           * targetUserHandle is either a hard user handle or is USER_ALL
           */
          public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
                                             boolean observerWantsSelfNotifications, int flags,
                                             int targetUserHandle, ArrayList<ObserverCall> calls) {
              String segment = null;
              int segmentCount = countUriSegments(uri);
              if (index >= segmentCount) {
                  // This is the leaf node, notify all observers
                  if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
                  collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
                          flags, targetUserHandle, calls);
              } else if (index < segmentCount){
                  segment = getUriSegment(uri, index);
                  if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
                          + segment);
                  // Notify any observers at this level who are interested in descendants
                  collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
                          flags, 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, flags, targetUserHandle, calls);
                      if (segment != null) {
                          break;
                      }
                  }
              }
          }
      }
  
      private void enforceShell(String method) {
          final int callingUid = Binder.getCallingUid();
          if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
              throw new SecurityException("Non-shell user attempted to call " + method);
          }
      }
  
      @Override
      public void resetTodayStats() {
          enforceShell("resetTodayStats");
  
          if (mSyncManager != null) {
              final long token = Binder.clearCallingIdentity();
              try {
                  mSyncManager.resetTodayStats();
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
          }
      }
  
      @Override
      public void onDbCorruption(String tag, String message, String stacktrace) {
          Slog.e(tag, message);
          Slog.e(tag, "at " + stacktrace);
  
          // TODO: Figure out a better way to report it. b/117886381
          Slog.wtf(tag, message);
      }
  
      @Override
      public void onShellCommand(FileDescriptor in, FileDescriptor out,
              FileDescriptor err, String[] args, ShellCallback callback,
              ResultReceiver resultReceiver) {
          (new ContentShellCommand(this)).exec(this, in, out, err, args, callback, resultReceiver);
      }
  }
  