
src.com.android.server.content.ContentService Maven / Gradle / Ivy
/*
* 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 static android.content.PermissionChecker.PERMISSION_GRANTED;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManagerInternal;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager.RestrictionLevel;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.compat.CompatChanges;
import android.app.job.JobInfo;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
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.ProviderInfo;
import android.database.IContentObserver;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.AppBackgroundRestrictionsInfo;
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.text.format.DateUtils;
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.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderDeathDispatcher;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
/**
* {@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;
/**
* Delay to apply to content change notifications dispatched to apps running
* in the background. This is used to help prevent stampeding when the user
* is performing CPU/RAM intensive foreground tasks, such as when capturing
* burst photos.
*/
private static final long BACKGROUND_OBSERVER_DELAY = 10 * DateUtils.SECOND_IN_MILLIS;
/**
* Enables checking for account access for the calling uid on all sync-related APIs.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2)
public static final long ACCOUNT_ACCESS_CHECK_CHANGE_ID = 201794303L;
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 onUserStarting(@NonNull TargetUser user) {
mService.onStartUser(user.getUserIdentifier());
}
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
mService.onUnlockUser(user.getUserIdentifier());
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
mService.onStopUser(user.getUserIdentifier());
}
@Override
public void onUserStopped(@NonNull TargetUser user) {
synchronized (mService.mCache) {
mService.mCache.remove(user.getUserIdentifier());
}
}
}
private Context mContext;
private boolean mFactoryTest;
private final ObserverNode mRootNode = new ObserverNode("");
private SyncManager mSyncManager = null;
private final Object mSyncManagerLock = new Object();
private final AccountManagerInternal mAccountManagerInternal;
private static final BinderDeathDispatcher sObserverDeathDispatcher =
new BinderDeathDispatcher<>();
@GuardedBy("sObserverLeakDetectedUid")
private static final ArraySet 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, 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 sorted = new ArrayList();
for (int i=0; i() {
@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 {
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);
mAccountManagerInternal = LocalServices.getService(AccountManagerInternal.class);
}
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[] uris, IContentObserver observer,
boolean observerWantsSelfNotifications, int flags, int userId,
int targetSdkVersion, String callingPackage) {
if (DEBUG) {
Slog.d(TAG, "Notifying update of " + Arrays.toString(uris) + " for user " + userId
+ ", observer " + observer + ", flags " + Integer.toHexString(flags));
}
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
// Set of notification events that we need to dispatch
final ObserverCollector collector = new ObserverCollector();
// Set of content provider authorities that we've validated the caller
// has access to, mapped to the package name hosting that provider
final ArrayMap, String> validatedProviders = new ArrayMap<>();
for (Uri uri : uris) {
// Validate that calling app has access to this provider
final int resolvedUserId = handleIncomingUser(uri, callingPid, callingUid,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true, userId);
final Pair provider = Pair.create(uri.getAuthority(), resolvedUserId);
if (!validatedProviders.containsKey(provider)) {
final String msg = LocalServices.getService(ActivityManagerInternal.class)
.checkContentProviderAccess(uri.getAuthority(), resolvedUserId);
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);
continue;
}
}
}
// Remember that we've validated this access
final String packageName = getProviderPackageName(uri, resolvedUserId);
validatedProviders.put(provider, packageName);
}
// No concerns raised above, so caller has access; let's collect the
// notifications that should be dispatched
synchronized (mRootNode) {
final int segmentCount = ObserverNode.countUriSegments(uri);
mRootNode.collectObserversLocked(uri, segmentCount, 0, observer,
observerWantsSelfNotifications, flags, resolvedUserId, collector);
}
}
final long token = clearCallingIdentity();
try {
// Actually dispatch all the notifications we collected
collector.dispatch();
for (int i = 0; i < validatedProviders.size(); i++) {
final String authority = validatedProviders.keyAt(i).first;
final int resolvedUserId = validatedProviders.keyAt(i).second;
final String packageName = validatedProviders.valueAt(i);
// Kick off sync adapters for any authorities we touched
if ((flags & ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleLocalSync(null /* all accounts */, callingUserId,
callingUid,
authority, getSyncExemptionForCaller(callingUid),
callingUid, callingPid, callingPackage);
}
}
// Invalidate caches for any authorities we touched
synchronized (mCache) {
for (Uri uri : uris) {
if (Objects.equals(uri.getAuthority(), authority)) {
invalidateCacheLocked(resolvedUserId, packageName, uri);
}
}
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
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;
}
}
/**
* Collection of detected change notifications that should be delivered.
*
* To help reduce Binder transaction overhead, this class clusters together
* multiple {@link Uri} where all other arguments are identical.
*/
@VisibleForTesting
public static class ObserverCollector {
private final ArrayMap> collected = new ArrayMap<>();
private static class Key {
final IContentObserver observer;
final int uid;
final boolean selfChange;
final int flags;
final int userId;
Key(IContentObserver observer, int uid, boolean selfChange, int flags, int userId) {
this.observer = observer;
this.uid = uid;
this.selfChange = selfChange;
this.flags = flags;
this.userId = userId;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Key)) {
return false;
}
final Key other = (Key) o;
return Objects.equals(observer, other.observer)
&& (uid == other.uid)
&& (selfChange == other.selfChange)
&& (flags == other.flags)
&& (userId == other.userId);
}
@Override
public int hashCode() {
return Objects.hash(observer, uid, selfChange, flags, userId);
}
}
public void collect(IContentObserver observer, int uid, boolean selfChange, Uri uri,
int flags, int userId) {
final Key key = new Key(observer, uid, selfChange, flags, userId);
List value = collected.get(key);
if (value == null) {
value = new ArrayList<>();
collected.put(key, value);
}
value.add(uri);
}
public void dispatch() {
for (int i = 0; i < collected.size(); i++) {
final Key key = collected.keyAt(i);
final List value = collected.valueAt(i);
final Runnable task = () -> {
try {
key.observer.onChangeEtc(key.selfChange,
value.toArray(new Uri[value.size()]), key.flags, key.userId);
} catch (RemoteException ignored) {
}
};
// Immediately dispatch notifications to foreground apps that
// are important to the user; all other background observers are
// delayed to avoid stampeding
final boolean noDelay = (key.flags & ContentResolver.NOTIFY_NO_DELAY) != 0;
final int procState = LocalServices.getService(ActivityManagerInternal.class)
.getUidProcessState(key.uid);
if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || noDelay) {
task.run();
} else {
BackgroundThread.getHandler().postDelayed(task, BACKGROUND_OBSERVER_DELAY);
}
}
}
}
@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();
if (!hasAccountAccess(true, account, callingUid)) {
return;
}
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.
final 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();
if (!hasAccountAccess(true, request.getAccount(), callingUid)) {
return;
}
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.
final 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.
*
* 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.
final 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);
final 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.
*
*
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);
final int callingUid = Binder.getCallingUid();
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
return syncManager.getSyncAdapterTypes(callingUid, userId);
} finally {
restoreCallingIdentity(identityToken);
}
}
@Override
public String[] getSyncAdapterPackagesForAuthorityAsUser(String authority, int userId) {
enforceCrossUserPermission(userId,
"no permission to read sync settings for user " + userId);
final int callingUid = Binder.getCallingUid();
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
return syncManager.getSyncAdapterPackagesForAuthorityAsUser(authority, callingUid,
userId);
} finally {
restoreCallingIdentity(identityToken);
}
}
@Override
public String getSyncAdapterPackageAsUser(@NonNull String accountType,
@NonNull String authority, @UserIdInt int userId) {
enforceCrossUserPermission(userId,
"no permission to read sync settings for user " + userId);
final int callingUid = Binder.getCallingUid();
final long identityToken = clearCallingIdentity();
try {
return getSyncManager().getSyncAdapterPackageAsUser(accountType, authority,
callingUid, 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");
if (!hasAccountAccess(true, account, Binder.getCallingUid())) {
return false;
}
final 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();
if (!hasAccountAccess(true, account, callingUid)) {
return;
}
final int syncExemptionFlag = getSyncExemptionForCaller(callingUid);
final 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");
final int callingUid = Binder.getCallingUid();
if (!hasAccountAccess(true, account, callingUid)) {
return;
}
validateExtras(callingUid, extras);
int userId = UserHandle.getCallingUserId();
pollFrequency = clampPeriod(pollFrequency);
long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
final 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");
final int callingUid = Binder.getCallingUid();
if (!hasAccountAccess(true, account, callingUid)) {
return;
}
validateExtras(callingUid, extras);
int userId = UserHandle.getCallingUserId();
final long identityToken = clearCallingIdentity();
try {
getSyncManager()
.removePeriodicSync(
new SyncStorageEngine.EndPoint(account, authority, userId),
extras, "removePeriodicSync() by uid=" + callingUid);
} finally {
restoreCallingIdentity(identityToken);
}
}
@Override
public List 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");
if (!hasAccountAccess(true, account, Binder.getCallingUid())) {
return new ArrayList<>(); // return a new empty list for consistent behavior
}
int userId = UserHandle.getCallingUserId();
final 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");
if (!hasAccountAccess(true, account, Binder.getCallingUid())) {
return SyncStorageEngine.AuthorityInfo.NOT_SYNCABLE; // to keep behavior consistent
}
final 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();
if (!hasAccountAccess(true, account, callingUid)) {
return;
}
final 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");
final 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();
final 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");
if (!hasAccountAccess(true, account, Binder.getCallingUid())) {
return false;
}
int userId = UserHandle.getCallingUserId();
final 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 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 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;
final 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");
if (!hasAccountAccess(true, account, Binder.getCallingUid())) {
return null;
}
final 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);
if (!hasAccountAccess(true, account, Binder.getCallingUid())) {
return false;
}
final 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();
final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null && callback != null) {
syncManager.getSyncStorageEngine().addStatusChangeListener(
mask, callingUid, callback);
}
} finally {
restoreCallingIdentity(identityToken);
}
}
@Override
public void removeStatusChangeListener(ISyncStatusObserver callback) {
final 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, int userId) {
final ProviderInfo pi = mContext.getPackageManager().resolveContentProviderAsUser(
uri.getAuthority(), 0, userId);
return (pi != null) ? pi.packageName : null;
}
@GuardedBy("mCache")
private ArrayMap, Bundle> findOrCreateCacheLocked(int userId,
String providerPackageName) {
ArrayMap, Bundle>> userCache = mCache.get(userId);
if (userCache == null) {
userCache = new ArrayMap<>();
mCache.put(userId, userCache);
}
ArrayMap, 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, Bundle>> userCache = mCache.get(userId);
if (userCache == null) return;
ArrayMap, Bundle> packageCache = userCache.get(providerPackageName);
if (packageCache == null) return;
if (uri != null) {
for (int i = 0; i < packageCache.size();) {
final Pair 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);
enforceNonFullCrossUserPermission(userId, TAG);
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CACHE_CONTENT, TAG);
mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
packageName);
final String providerPackageName = getProviderPackageName(key, userId);
final Pair fullKey = Pair.create(packageName, key);
synchronized (mCache) {
final ArrayMap, 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) {
enforceNonFullCrossUserPermission(userId, TAG);
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CACHE_CONTENT, TAG);
mContext.getSystemService(AppOpsManager.class).checkPackage(Binder.getCallingUid(),
packageName);
final String providerPackageName = getProviderPackageName(key, userId);
final Pair fullKey = Pair.create(packageName, key);
synchronized (mCache) {
final ArrayMap, 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);
}
}
/**
* Checks if the request is from the system or an app that has {@code INTERACT_ACROSS_USERS} or
* {@code INTERACT_ACROSS_USERS_FULL} permission, if the {@code 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 enforceNonFullCrossUserPermission(int userHandle, String message) {
final int callingUser = UserHandle.getCallingUserId();
if (callingUser == userHandle) {
return;
}
int interactAcrossUsersState = mContext.checkCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS);
if (interactAcrossUsersState == PERMISSION_GRANTED) {
return;
}
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
}
/**
* Checks to see if the given account is accessible by the provided uid.
*
* @param checkCompatFlag whether to check if the ACCOUNT_ACCESS_CHECK_CHANGE_ID flag is enabled
* @param account the account trying to be accessed
* @param uid the uid trying to access the account
* @return {@code true} if the account is accessible by the given uid, {@code false} otherwise
*/
private boolean hasAccountAccess(boolean checkCompatFlag, Account account, int uid) {
if (account == null) {
// If the account is null, it means to check for all accounts hence skip the check here.
return true;
}
if (checkCompatFlag
&& !CompatChanges.isChangeEnabled(ACCOUNT_ACCESS_CHECK_CHANGE_ID, uid)) {
return true;
}
final long identityToken = clearCallingIdentity();
try {
return mAccountManagerInternal.hasAccountAccess(account, uid);
} finally {
restoreCallingIdentity(identityToken);
}
}
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) {
FrameworkStatsLog.write(FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED,
callingUid, getProcStateForStatsd(procState), isUidActive,
getRestrictionLevelForStatsd(ami.getRestrictionLevel(callingUid)));
return ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET;
}
return ContentResolver.SYNC_EXEMPTION_NONE;
}
private int getProcStateForStatsd(int procState) {
switch (procState) {
case ActivityManager.PROCESS_STATE_UNKNOWN:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__UNKNOWN;
case ActivityManager.PROCESS_STATE_PERSISTENT:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__PERSISTENT;
case ActivityManager.PROCESS_STATE_PERSISTENT_UI:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__PERSISTENT_UI;
case ActivityManager.PROCESS_STATE_TOP:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__TOP;
case ActivityManager.PROCESS_STATE_BOUND_TOP:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__BOUND_TOP;
case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__FOREGROUND_SERVICE;
case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
return FrameworkStatsLog
.SYNC_EXEMPTION_OCCURRED__PROC_STATE__BOUND_FOREGROUND_SERVICE;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__IMPORTANT_FOREGROUND;
case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__IMPORTANT_BACKGROUND;
case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__TRANSIENT_BACKGROUND;
case ActivityManager.PROCESS_STATE_BACKUP:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__BACKUP;
case ActivityManager.PROCESS_STATE_SERVICE:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__SERVICE;
case ActivityManager.PROCESS_STATE_RECEIVER:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__RECEIVER;
case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__TOP_SLEEPING;
case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__HEAVY_WEIGHT;
case ActivityManager.PROCESS_STATE_LAST_ACTIVITY:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__LAST_ACTIVITY;
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_ACTIVITY;
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
return FrameworkStatsLog
.SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_ACTIVITY_CLIENT;
case ActivityManager.PROCESS_STATE_CACHED_RECENT:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_RECENT;
case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__CACHED_EMPTY;
default:
return FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED__PROC_STATE__UNKNOWN;
}
}
private int getRestrictionLevelForStatsd(@RestrictionLevel int level) {
switch (level) {
case ActivityManager.RESTRICTION_LEVEL_UNKNOWN:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
case ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_UNRESTRICTED;
case ActivityManager.RESTRICTION_LEVEL_EXEMPTED:
return AppBackgroundRestrictionsInfo.LEVEL_EXEMPTED;
case ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
return AppBackgroundRestrictionsInfo.LEVEL_ADAPTIVE_BUCKET;
case ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET:
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
case ActivityManager.RESTRICTION_LEVEL_HIBERNATION:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
}
}
/** {@hide} */
@VisibleForTesting
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)));
}
}
private String mName;
private ArrayList mChildren = new ArrayList();
private ArrayList mObservers = new ArrayList();
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 0) {
if (innerName == null) {
if ("".equals(name)) {
innerName = mName;
} else {
innerName = name + "/" + mName;
}
}
for (int i=0; i