blob: 2672f848f1920be0d46019f1f4534b8e3a4db4a7 [file] [log] [blame]
/*
* Copyright (C) 2018 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.infra;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Base class for {@link SystemService SystemServices} that support multi user.
*
* <p>Subclasses of this service are just a facade for the service binder calls - the "real" work
* is done by the {@link AbstractPerUserSystemService} subclasses, which are automatically managed
* through an user -> service cache.
*
* <p>It also takes care of other plumbing tasks such as:
*
* <ul>
* <li>Disabling the service when {@link UserManager} restrictions change.
* <li>Refreshing the service when its underlying
* {@link #getServiceSettingsProperty() Settings property} changed.
* <li>Calling the service when other Settings properties changed.
* </ul>
*
* <p>See {@code com.android.server.autofill.AutofillManagerService} for a concrete
* (no pun intended) example of how to use it.
*
* @param <M> "master" service class.
* @param <S> "real" service class.
*
* @hide
*/
// TODO(b/117779333): improve javadoc above instead of using Autofill as an example
public abstract class AbstractMasterSystemService<M extends AbstractMasterSystemService<M, S>,
S extends AbstractPerUserSystemService<S, M>> extends SystemService {
/** On a package update, does not refresh the per-user service in the cache. */
public static final int PACKAGE_UPDATE_POLICY_NO_REFRESH = 0x00000001;
/**
* On a package update, removes any existing per-user services in the cache.
*
* <p>This does not immediately recreate these services. It is assumed they will be recreated
* for the next user request.
*/
public static final int PACKAGE_UPDATE_POLICY_REFRESH_LAZY = 0x00000002;
/**
* On a package update, removes and recreates any existing per-user services in the cache.
*/
public static final int PACKAGE_UPDATE_POLICY_REFRESH_EAGER = 0x00000004;
/** On a package restart, does not refresh the per-user service in the cache. */
public static final int PACKAGE_RESTART_POLICY_NO_REFRESH = 0x00000010;
/**
* On a package restart, removes any existing per-user services in the cache.
*
* <p>This does not immediately recreate these services. It is assumed they will be recreated
* for the next user request.
*/
public static final int PACKAGE_RESTART_POLICY_REFRESH_LAZY = 0x00000020;
/**
* On a package restart, removes and recreates any existing per-user services in the cache.
*/
public static final int PACKAGE_RESTART_POLICY_REFRESH_EAGER = 0x00000040;
@IntDef(flag = true, prefix = { "PACKAGE_" }, value = {
PACKAGE_UPDATE_POLICY_NO_REFRESH,
PACKAGE_UPDATE_POLICY_REFRESH_LAZY,
PACKAGE_UPDATE_POLICY_REFRESH_EAGER,
PACKAGE_RESTART_POLICY_NO_REFRESH,
PACKAGE_RESTART_POLICY_REFRESH_LAZY,
PACKAGE_RESTART_POLICY_REFRESH_EAGER
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServicePackagePolicyFlags {}
/**
* Log tag
*/
protected final String mTag = getClass().getSimpleName();
/**
* Lock used to synchronize access to internal state; should be acquired before calling a
* method whose name ends with {@code locked}.
*/
protected final Object mLock = new Object();
/**
* Object used to define the name of the service component used to create
* {@link com.android.internal.infra.AbstractRemoteService} instances.
*/
@Nullable
protected final ServiceNameResolver mServiceNameResolver;
/**
* Whether the service should log debug statements.
*/
//TODO(b/117779333): consider using constants for these guards
public boolean verbose = false;
/**
* Whether the service should log verbose statements.
*/
//TODO(b/117779333): consider using constants for these guards
public boolean debug = false;
/**
* Whether the service is allowed to bind to an instant-app.
*/
@GuardedBy("mLock")
protected boolean mAllowInstantService;
/**
* Users disabled due to {@link UserManager} restrictions, or {@code null} if the service cannot
* be disabled through {@link UserManager}.
*/
@GuardedBy("mLock")
@Nullable
private final SparseBooleanArray mDisabledByUserRestriction;
/**
* Cache of services per user id.
*/
@GuardedBy("mLock")
private final SparseArray<S> mServicesCache = new SparseArray<>();
/**
* Value that determines whether the per-user service should be removed from the cache when its
* apk is updated or restarted.
*/
private final @ServicePackagePolicyFlags int mServicePackagePolicyFlags;
/**
* Name of the service packages whose APK are being updated, keyed by user id.
*/
@GuardedBy("mLock")
private SparseArray<String> mUpdatingPackageNames;
/**
* Lazy-loadable reference to {@link UserManagerInternal}.
*/
@Nullable
private UserManagerInternal mUm;
/**
* Default constructor.
*
* <p>When using this constructor, the {@link AbstractPerUserSystemService} is removed from
* the cache (and re-added) when the service package is updated.
*
* @param context system context.
* @param serviceNameResolver resolver for
* {@link com.android.internal.infra.AbstractRemoteService} instances, or
* {@code null} when the service doesn't bind to remote services.
* @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that
* disables the service. <b>NOTE: </b> you'll also need to add it to
* {@code UserRestrictionsUtils.USER_RESTRICTIONS}.
*/
protected AbstractMasterSystemService(@NonNull Context context,
@Nullable ServiceNameResolver serviceNameResolver,
@Nullable String disallowProperty) {
this(context, serviceNameResolver, disallowProperty,
PACKAGE_UPDATE_POLICY_REFRESH_LAZY | PACKAGE_RESTART_POLICY_REFRESH_LAZY);
}
/**
* Full Constructor.
*
* @param context system context.
* @param serviceNameResolver resolver for
* {@link com.android.internal.infra.AbstractRemoteService} instances, or
* {@code null} when the service doesn't bind to remote services.
* @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that
* disables the service. <b>NOTE: </b> you'll also need to add it to
* {@code UserRestrictionsUtils.USER_RESTRICTIONS}.
* @param servicePackagePolicyFlags a combination of
* {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH},
* {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY},
* {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER},
* {@link #PACKAGE_RESTART_POLICY_NO_REFRESH},
* {@link #PACKAGE_RESTART_POLICY_REFRESH_LAZY} or
* {@link #PACKAGE_RESTART_POLICY_REFRESH_EAGER}
*/
protected AbstractMasterSystemService(@NonNull Context context,
@Nullable ServiceNameResolver serviceNameResolver, @Nullable String disallowProperty,
@ServicePackagePolicyFlags int servicePackagePolicyFlags) {
super(context);
final int updatePolicyMask = PACKAGE_UPDATE_POLICY_NO_REFRESH
| PACKAGE_UPDATE_POLICY_REFRESH_LAZY | PACKAGE_UPDATE_POLICY_REFRESH_EAGER;
if ((servicePackagePolicyFlags & updatePolicyMask) == 0) {
// If the package update policy is not set, add the default flag
servicePackagePolicyFlags |= PACKAGE_UPDATE_POLICY_REFRESH_LAZY;
}
final int restartPolicyMask = PACKAGE_RESTART_POLICY_NO_REFRESH
| PACKAGE_RESTART_POLICY_REFRESH_LAZY | PACKAGE_RESTART_POLICY_REFRESH_EAGER;
if ((servicePackagePolicyFlags & restartPolicyMask) == 0) {
// If the package restart policy is not set, add the default flag
servicePackagePolicyFlags |= PACKAGE_RESTART_POLICY_REFRESH_LAZY;
}
mServicePackagePolicyFlags = servicePackagePolicyFlags;
mServiceNameResolver = serviceNameResolver;
if (mServiceNameResolver != null) {
mServiceNameResolver.setOnTemporaryServiceNameChangedCallback(
(u, s, t) -> onServiceNameChanged(u, s, t));
}
if (disallowProperty == null) {
mDisabledByUserRestriction = null;
} else {
mDisabledByUserRestriction = new SparseBooleanArray();
// Hookup with UserManager to disable service when necessary.
final UserManagerInternal umi = getUserManagerInternal();
final List<UserInfo> users = getSupportedUsers();
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
final boolean disabled = umi.getUserRestriction(userId, disallowProperty);
if (disabled) {
Slog.i(mTag, "Disabling by restrictions user " + userId);
mDisabledByUserRestriction.put(userId, disabled);
}
}
umi.addUserRestrictionsListener((userId, newRestrictions, prevRestrictions) -> {
final boolean disabledNow =
newRestrictions.getBoolean(disallowProperty, false);
synchronized (mLock) {
final boolean disabledBefore = mDisabledByUserRestriction.get(userId);
if (disabledBefore == disabledNow) {
// Nothing changed, do nothing.
if (debug) {
Slog.d(mTag, "Restriction did not change for user " + userId);
return;
}
}
Slog.i(mTag, "Updating for user " + userId + ": disabled=" + disabledNow);
mDisabledByUserRestriction.put(userId, disabledNow);
updateCachedServiceLocked(userId, disabledNow);
}
});
}
startTrackingPackageChanges();
}
@Override // from SystemService
public void onBootPhase(int phase) {
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
new SettingsObserver(BackgroundThread.getHandler());
}
}
@Override // from SystemService
public void onUnlockUser(int userId) {
synchronized (mLock) {
updateCachedServiceLocked(userId);
}
}
@Override // from SystemService
public void onCleanupUser(int userId) {
synchronized (mLock) {
removeCachedServiceLocked(userId);
}
}
/**
* Gets whether the service is allowed to bind to an instant-app.
*
* <p>Typically called by {@code ShellCommand} during CTS tests.
*
* @throws SecurityException if caller is not allowed to manage this service's settings.
*/
public final boolean getAllowInstantService() {
enforceCallingPermissionForManagement();
synchronized (mLock) {
return mAllowInstantService;
}
}
/**
* Checks whether the service is allowed to bind to an instant-app.
*
* <p>Typically called by subclasses when creating {@link AbstractRemoteService} instances.
*
* <p><b>NOTE: </b>must not be called by {@code ShellCommand} as it does not check for
* permission.
*/
public final boolean isBindInstantServiceAllowed() {
synchronized (mLock) {
return mAllowInstantService;
}
}
/**
* Sets whether the service is allowed to bind to an instant-app.
*
* <p>Typically called by {@code ShellCommand} during CTS tests.
*
* @throws SecurityException if caller is not allowed to manage this service's settings.
*/
public final void setAllowInstantService(boolean mode) {
Slog.i(mTag, "setAllowInstantService(): " + mode);
enforceCallingPermissionForManagement();
synchronized (mLock) {
mAllowInstantService = mode;
}
}
/**
* Temporarily sets the service implementation.
*
* <p>Typically used by Shell command and/or CTS tests.
*
* @param componentName name of the new component
* @param durationMs how long the change will be valid (the service will be automatically reset
* to the default component after this timeout expires).
* @throws SecurityException if caller is not allowed to manage this service's settings.
* @throws IllegalArgumentException if value of {@code durationMs} is higher than
* {@link #getMaximumTemporaryServiceDurationMs()}.
*/
public final void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
int durationMs) {
Slog.i(mTag, "setTemporaryService(" + userId + ") to " + componentName + " for "
+ durationMs + "ms");
enforceCallingPermissionForManagement();
Objects.requireNonNull(componentName);
final int maxDurationMs = getMaximumTemporaryServiceDurationMs();
if (durationMs > maxDurationMs) {
throw new IllegalArgumentException(
"Max duration is " + maxDurationMs + " (called with " + durationMs + ")");
}
synchronized (mLock) {
final S oldService = peekServiceForUserLocked(userId);
if (oldService != null) {
oldService.removeSelfFromCacheLocked();
}
mServiceNameResolver.setTemporaryService(userId, componentName, durationMs);
}
}
/**
* Sets whether the default service should be used.
*
* <p>Typically used during CTS tests to make sure only the default service doesn't interfere
* with the test results.
*
* @throws SecurityException if caller is not allowed to manage this service's settings.
*
* @return whether the enabled state changed.
*/
public final boolean setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
Slog.i(mTag, "setDefaultServiceEnabled() for userId " + userId + ": " + enabled);
enforceCallingPermissionForManagement();
synchronized (mLock) {
final boolean changed = mServiceNameResolver.setDefaultServiceEnabled(userId, enabled);
if (!changed) {
if (verbose) {
Slog.v(mTag, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
}
return false;
}
final S oldService = peekServiceForUserLocked(userId);
if (oldService != null) {
oldService.removeSelfFromCacheLocked();
}
// Must update the service on cache so its initialization code is triggered
updateCachedServiceLocked(userId);
}
return true;
}
/**
* Checks whether the default service should be used.
*
* <p>Typically used during CTS tests to make sure only the default service doesn't interfere
* with the test results.
*
* @throws SecurityException if caller is not allowed to manage this service's settings.
*/
public final boolean isDefaultServiceEnabled(@UserIdInt int userId) {
enforceCallingPermissionForManagement();
synchronized (mLock) {
return mServiceNameResolver.isDefaultServiceEnabled(userId);
}
}
/**
* Gets the maximum time the service implementation can be changed.
*
* @throws UnsupportedOperationException if subclass doesn't override it.
*/
protected int getMaximumTemporaryServiceDurationMs() {
throw new UnsupportedOperationException("Not implemented by " + getClass());
}
/**
* Resets the temporary service implementation to the default component.
*
* <p>Typically used by Shell command and/or CTS tests.
*
* @throws SecurityException if caller is not allowed to manage this service's settings.
*/
public final void resetTemporaryService(@UserIdInt int userId) {
Slog.i(mTag, "resetTemporaryService(): " + userId);
enforceCallingPermissionForManagement();
synchronized (mLock) {
final S service = getServiceForUserLocked(userId);
if (service != null) {
service.resetTemporaryServiceLocked();
}
}
}
/**
* Asserts that the caller has permissions to manage this service.
*
* <p>Typically called by {@code ShellCommand} implementations.
*
* @throws UnsupportedOperationException if subclass doesn't override it.
* @throws SecurityException if caller is not allowed to manage this service's settings.
*/
protected void enforceCallingPermissionForManagement() {
throw new UnsupportedOperationException("Not implemented by " + getClass());
}
/**
* Creates a new service that will be added to the cache.
*
* @param resolvedUserId the resolved user id for the service.
* @param disabled whether the service is currently disabled (due to {@link UserManager}
* restrictions).
*
* @return a new instance.
*/
@Nullable
protected abstract S newServiceLocked(@UserIdInt int resolvedUserId, boolean disabled);
/**
* Register the service for extra Settings changes (i.e., other than
* {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} or
* {@link #getServiceSettingsProperty()}, which are automatically handled).
*
* <p> Example:
*
* <pre><code>
* resolver.registerContentObserver(Settings.Global.getUriFor(
* Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, observer,
* UserHandle.USER_ALL);
* </code></pre>
*
* <p><b>NOTE: </p>it doesn't need to register for
* {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} or
* {@link #getServiceSettingsProperty()}.
*
*/
@SuppressWarnings("unused")
protected void registerForExtraSettingsChanges(@NonNull ContentResolver resolver,
@NonNull ContentObserver observer) {
}
/**
* Callback for Settings changes that were registered though
* {@link #registerForExtraSettingsChanges(ContentResolver, ContentObserver)}.
*
* @param userId user associated with the change
* @param property Settings property changed.
*/
protected void onSettingsChanged(@UserIdInt int userId, @NonNull String property) {
}
/**
* Gets the service instance for an user, creating an instance if not present in the cache.
*/
@GuardedBy("mLock")
@NonNull
protected S getServiceForUserLocked(@UserIdInt int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, null, null);
S service = mServicesCache.get(resolvedUserId);
if (service == null) {
final boolean disabled = isDisabledLocked(userId);
service = newServiceLocked(resolvedUserId, disabled);
if (!disabled) {
onServiceEnabledLocked(service, resolvedUserId);
}
mServicesCache.put(userId, service);
}
return service;
}
/**
* Gets the <b>existing</b> service instance for a user, returning {@code null} if not already
* present in the cache.
*/
@GuardedBy("mLock")
@Nullable
protected S peekServiceForUserLocked(@UserIdInt int userId) {
final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, false, null, null);
return mServicesCache.get(resolvedUserId);
}
/**
* Updates a cached service for a given user.
*/
@GuardedBy("mLock")
protected void updateCachedServiceLocked(@UserIdInt int userId) {
updateCachedServiceLocked(userId, isDisabledLocked(userId));
}
/**
* Checks whether the service is disabled (through {@link UserManager} restrictions) for the
* given user.
*/
protected boolean isDisabledLocked(@UserIdInt int userId) {
return mDisabledByUserRestriction == null ? false : mDisabledByUserRestriction.get(userId);
}
/**
* Updates a cached service for a given user.
*
* @param userId user handle.
* @param disabled whether the user is disabled.
* @return service for the user.
*/
@GuardedBy("mLock")
protected S updateCachedServiceLocked(@UserIdInt int userId, boolean disabled) {
final S service = getServiceForUserLocked(userId);
if (service != null) {
service.updateLocked(disabled);
if (!service.isEnabledLocked()) {
removeCachedServiceLocked(userId);
} else {
onServiceEnabledLocked(service, userId);
}
}
return service;
}
/**
* Gets the Settings property that defines the name of the component name used to bind this
* service to an external service, or {@code null} when the service is not defined by such
* property (for example, if it's a system service defined by framework resources).
*/
@Nullable
protected String getServiceSettingsProperty() {
return null;
}
/**
* Callback called after a new service was added to the cache, or an existing service that was
* previously disabled gets enabled.
*
* <p>By default doesn't do anything, but can be overridden by subclasses.
*/
@SuppressWarnings("unused")
protected void onServiceEnabledLocked(@NonNull S service, @UserIdInt int userId) {
}
/**
* Removes a cached service for a given user.
*
* @return the removed service.
*/
@GuardedBy("mLock")
@NonNull
protected final S removeCachedServiceLocked(@UserIdInt int userId) {
final S service = peekServiceForUserLocked(userId);
if (service != null) {
mServicesCache.delete(userId);
onServiceRemoved(service, userId);
}
return service;
}
/**
* Called before the package that provides the service for the given user is being updated.
*/
protected void onServicePackageUpdatingLocked(@UserIdInt int userId) {
if (verbose) Slog.v(mTag, "onServicePackageUpdatingLocked(" + userId + ")");
}
/**
* Called after the package that provides the service for the given user is being updated.
*/
protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
if (verbose) Slog.v(mTag, "onServicePackageUpdated(" + userId + ")");
}
/**
* Called after the package data that provides the service for the given user is cleared.
*/
protected void onServicePackageDataClearedLocked(@UserIdInt int userId) {
if (verbose) Slog.v(mTag, "onServicePackageDataCleared(" + userId + ")");
}
/**
* Called after the package that provides the service for the given user is restarted.
*/
protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
if (verbose) Slog.v(mTag, "onServicePackageRestarted(" + userId + ")");
}
/**
* Called after the service is removed from the cache.
*/
@SuppressWarnings("unused")
protected void onServiceRemoved(@NonNull S service, @UserIdInt int userId) {
}
/**
* Called when the service name changed (typically when using temporary services).
*
* <p>By default, it calls {@link #updateCachedServiceLocked(int)}; subclasses must either call
* that same method, or {@code super.onServiceNameChanged()}.
*
* @param userId user handle.
* @param serviceName the new service name.
* @param isTemporary whether the new service is temporary.
*/
protected void onServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName,
boolean isTemporary) {
synchronized (mLock) {
updateCachedServiceLocked(userId);
}
}
/**
* Visits all services in the cache.
*/
@GuardedBy("mLock")
protected void visitServicesLocked(@NonNull Visitor<S> visitor) {
final int size = mServicesCache.size();
for (int i = 0; i < size; i++) {
visitor.visit(mServicesCache.valueAt(i));
}
}
/**
* Clear the cache by removing all services.
*/
@GuardedBy("mLock")
protected void clearCacheLocked() {
mServicesCache.clear();
}
/**
* Gets a cached reference to {@link UserManagerInternal}.
*/
@NonNull
protected UserManagerInternal getUserManagerInternal() {
if (mUm == null) {
if (verbose) Slog.v(mTag, "lazy-loading UserManagerInternal");
mUm = LocalServices.getService(UserManagerInternal.class);
}
return mUm;
}
/**
* Gets a list of all supported users (i.e., those that pass the
* {@link #isUserSupported(TargetUser)}check).
*/
@NonNull
protected List<UserInfo> getSupportedUsers() {
final UserInfo[] allUsers = getUserManagerInternal().getUserInfos();
final int size = allUsers.length;
final List<UserInfo> supportedUsers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final UserInfo userInfo = allUsers[i];
if (isUserSupported(new TargetUser(userInfo))) {
supportedUsers.add(userInfo);
}
}
return supportedUsers;
}
/**
* Asserts that the given package name is owned by the UID making this call.
*
* @throws SecurityException when it's not...
*/
protected final void assertCalledByPackageOwner(@NonNull String packageName) {
Objects.requireNonNull(packageName);
final int uid = Binder.getCallingUid();
final String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
if (packages != null) {
for (String candidate : packages) {
if (packageName.equals(candidate)) return; // Found it
}
}
throw new SecurityException("UID " + uid + " does not own " + packageName);
}
// TODO(b/117779333): support proto
protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
boolean realDebug = debug;
boolean realVerbose = verbose;
final String prefix2 = " ";
try {
// Temporarily turn on full logging;
debug = verbose = true;
final int size = mServicesCache.size();
pw.print(prefix); pw.print("Debug: "); pw.print(realDebug);
pw.print(" Verbose: "); pw.println(realVerbose);
pw.print("Package policy flags: "); pw.println(mServicePackagePolicyFlags);
if (mUpdatingPackageNames != null) {
pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
}
dumpSupportedUsers(pw, prefix);
if (mServiceNameResolver != null) {
pw.print(prefix); pw.print("Name resolver: ");
mServiceNameResolver.dumpShort(pw); pw.println();
final List<UserInfo> users = getSupportedUsers();
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
pw.print(prefix2); pw.print(userId); pw.print(": ");
mServiceNameResolver.dumpShort(pw, userId); pw.println();
}
}
pw.print(prefix); pw.print("Users disabled by restriction: ");
pw.println(mDisabledByUserRestriction);
pw.print(prefix); pw.print("Allow instant service: "); pw.println(mAllowInstantService);
final String settingsProperty = getServiceSettingsProperty();
if (settingsProperty != null) {
pw.print(prefix); pw.print("Settings property: "); pw.println(settingsProperty);
}
pw.print(prefix); pw.print("Cached services: ");
if (size == 0) {
pw.println("none");
} else {
pw.println(size);
for (int i = 0; i < size; i++) {
pw.print(prefix); pw.print("Service at "); pw.print(i); pw.println(": ");
final S service = mServicesCache.valueAt(i);
service.dumpLocked(prefix2, pw);
pw.println();
}
}
} finally {
debug = realDebug;
verbose = realVerbose;
}
}
private void startTrackingPackageChanges() {
final PackageMonitor monitor = new PackageMonitor() {
@Override
public void onPackageUpdateStarted(@NonNull String packageName, int uid) {
if (verbose) Slog.v(mTag, "onPackageUpdateStarted(): " + packageName);
final String activePackageName = getActiveServicePackageNameLocked();
if (!packageName.equals(activePackageName)) return;
final int userId = getChangingUserId();
synchronized (mLock) {
if (mUpdatingPackageNames == null) {
mUpdatingPackageNames = new SparseArray<String>(mServicesCache.size());
}
mUpdatingPackageNames.put(userId, packageName);
onServicePackageUpdatingLocked(userId);
if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_NO_REFRESH) != 0) {
if (debug) {
Slog.d(mTag, "Holding service for user " + userId + " while package "
+ activePackageName + " is being updated");
}
} else {
if (debug) {
Slog.d(mTag, "Removing service for user " + userId
+ " because package " + activePackageName
+ " is being updated");
}
removeCachedServiceLocked(userId);
if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_REFRESH_EAGER)
!= 0) {
if (debug) {
Slog.d(mTag, "Eagerly recreating service for user "
+ userId);
}
getServiceForUserLocked(userId);
}
}
}
}
@Override
public void onPackageUpdateFinished(@NonNull String packageName, int uid) {
if (verbose) Slog.v(mTag, "onPackageUpdateFinished(): " + packageName);
final int userId = getChangingUserId();
synchronized (mLock) {
final String activePackageName = mUpdatingPackageNames == null ? null
: mUpdatingPackageNames.get(userId);
if (packageName.equals(activePackageName)) {
if (mUpdatingPackageNames != null) {
mUpdatingPackageNames.remove(userId);
if (mUpdatingPackageNames.size() == 0) {
mUpdatingPackageNames = null;
}
}
onServicePackageUpdatedLocked(userId);
} else {
handlePackageUpdateLocked(packageName);
}
}
}
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
final int userId = getChangingUserId();
final S service = peekServiceForUserLocked(userId);
if (service != null) {
final ComponentName componentName = service.getServiceComponentName();
if (componentName != null) {
if (packageName.equals(componentName.getPackageName())) {
handleActiveServiceRemoved(userId);
}
}
}
}
}
@Override
public boolean onHandleForceStop(Intent intent, String[] packages,
int uid, boolean doit) {
synchronized (mLock) {
final String activePackageName = getActiveServicePackageNameLocked();
for (String pkg : packages) {
if (pkg.equals(activePackageName)) {
if (!doit) {
return true;
}
final String action = intent.getAction();
final int userId = getChangingUserId();
if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
handleActiveServiceRestartedLocked(activePackageName, userId);
} else {
removeCachedServiceLocked(userId);
}
} else {
handlePackageUpdateLocked(pkg);
}
}
}
return false;
}
@Override
public void onPackageDataCleared(String packageName, int uid) {
if (verbose) Slog.v(mTag, "onPackageDataCleared(): " + packageName);
final int userId = getChangingUserId();
synchronized (mLock) {
final S service = peekServiceForUserLocked(userId);
if (service != null) {
final ComponentName componentName = service.getServiceComponentName();
if (componentName != null) {
if (packageName.equals(componentName.getPackageName())) {
onServicePackageDataClearedLocked(userId);
}
}
}
}
}
private void handleActiveServiceRemoved(@UserIdInt int userId) {
synchronized (mLock) {
removeCachedServiceLocked(userId);
}
final String serviceSettingsProperty = getServiceSettingsProperty();
if (serviceSettingsProperty != null) {
Settings.Secure.putStringForUser(getContext().getContentResolver(),
serviceSettingsProperty, null, userId);
}
}
private void handleActiveServiceRestartedLocked(String activePackageName,
@UserIdInt int userId) {
if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_NO_REFRESH) != 0) {
if (debug) {
Slog.d(mTag, "Holding service for user " + userId + " while package "
+ activePackageName + " is being restarted");
}
} else {
if (debug) {
Slog.d(mTag, "Removing service for user " + userId
+ " because package " + activePackageName
+ " is being restarted");
}
removeCachedServiceLocked(userId);
if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_REFRESH_EAGER) != 0) {
if (debug) {
Slog.d(mTag, "Eagerly recreating service for user " + userId);
}
getServiceForUserLocked(userId);
}
}
onServicePackageRestartedLocked(userId);
}
@Override
public void onPackageModified(String packageName) {
if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
final int userId = getChangingUserId();
final String serviceName = mServiceNameResolver.getDefaultServiceName(userId);
if (serviceName == null) {
return;
}
final ComponentName serviceComponentName =
ComponentName.unflattenFromString(serviceName);
if (serviceComponentName == null
|| !serviceComponentName.getPackageName().equals(packageName)) {
return;
}
// The default service package has changed, update the cached if the service
// exists but no active component.
final S service = peekServiceForUserLocked(userId);
if (service != null) {
final ComponentName componentName = service.getServiceComponentName();
if (componentName == null) {
if (verbose) Slog.v(mTag, "update cached");
updateCachedServiceLocked(userId);
}
}
}
private String getActiveServicePackageNameLocked() {
final int userId = getChangingUserId();
final S service = peekServiceForUserLocked(userId);
if (service == null) {
return null;
}
final ComponentName serviceComponent = service.getServiceComponentName();
if (serviceComponent == null) {
return null;
}
return serviceComponent.getPackageName();
}
@GuardedBy("mLock")
private void handlePackageUpdateLocked(String packageName) {
visitServicesLocked((s) -> s.handlePackageUpdateLocked(packageName));
}
};
// package changes
monitor.register(getContext(), null, UserHandle.ALL, true);
}
/**
* Visitor pattern.
*
* @param <S> visited class.
*/
public interface Visitor<S> {
/**
* Visits a service.
*
* @param service the service to be visited.
*/
void visit(@NonNull S service);
}
private final class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
ContentResolver resolver = getContext().getContentResolver();
final String serviceProperty = getServiceSettingsProperty();
if (serviceProperty != null) {
resolver.registerContentObserver(Settings.Secure.getUriFor(
serviceProperty), false, this, UserHandle.USER_ALL);
}
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.USER_SETUP_COMPLETE), false, this, UserHandle.USER_ALL);
registerForExtraSettingsChanges(resolver, this);
}
@Override
public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
if (verbose) Slog.v(mTag, "onChange(): uri=" + uri + ", userId=" + userId);
final String property = uri.getLastPathSegment();
if (property.equals(getServiceSettingsProperty())
|| property.equals(Settings.Secure.USER_SETUP_COMPLETE)) {
synchronized (mLock) {
updateCachedServiceLocked(userId);
}
} else {
onSettingsChanged(userId, property);
}
}
}
}