blob: 16cf7eef6a1e07d3b5117a921141d36c499d721b [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.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
/**
* Companion for {@link AbstractMasterSystemService}, it's the base class for the "real" service
* implementation.
*
* @param <M> "master" service class.
* @param <S> "real" service class.
*
* @hide
*/
public abstract class AbstractPerUserSystemService<S extends AbstractPerUserSystemService<S, M>,
M extends AbstractMasterSystemService<M, S>> {
protected final @UserIdInt int mUserId;
protected final Object mLock;
protected final String mTag = getClass().getSimpleName();
protected final M mMaster;
/**
* Whether service was disabled for user due to {@link UserManager} restrictions.
*/
@GuardedBy("mLock")
private boolean mDisabled;
/**
* Caches whether the setup completed for the current user.
*/
@GuardedBy("mLock")
private boolean mSetupComplete;
@GuardedBy("mLock")
private ServiceInfo mServiceInfo;
protected AbstractPerUserSystemService(@NonNull M master, @NonNull Object lock,
@UserIdInt int userId) {
mMaster = master;
mLock = lock;
mUserId = userId;
updateIsSetupComplete(userId);
}
/** Updates whether setup is complete for current user */
private void updateIsSetupComplete(@UserIdInt int userId) {
final String setupComplete = Settings.Secure.getStringForUser(
getContext().getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, userId);
mSetupComplete = "1".equals(setupComplete);
}
/**
* Creates a new {@link ServiceInfo} for the given service name.
*
* <p><b>MUST</b> be overridden by subclasses that bind to an
* {@link com.android.internal.infra.AbstractRemoteService}.
*
* @throws NameNotFoundException if the service does not exist.
* @throws SecurityException if the service does not have the proper permissions to be bound to.
* @throws UnsupportedOperationException if subclass binds to a remote service but does not
* overrides it.
*
* @return new {@link ServiceInfo},
*/
protected @NonNull ServiceInfo newServiceInfoLocked(
@SuppressWarnings("unused") @NonNull ComponentName serviceComponent)
throws NameNotFoundException {
throw new UnsupportedOperationException("not overridden");
}
/**
* Callback called when an app has been updated.
*
* @param packageName package of the app being updated.
*/
protected void handlePackageUpdateLocked(@NonNull String packageName) {
}
/**
* Gets whether the service is enabled and ready.
*/
@GuardedBy("mLock")
protected boolean isEnabledLocked() {
return mSetupComplete && mServiceInfo != null && !mDisabled;
}
/**
* Gets whether the service is disabled by {@link UserManager} restrictions.
*/
protected final boolean isDisabledByUserRestrictionsLocked() {
return mDisabled;
}
/**
* Updates the state of this service.
*
* <p>Typically called when the service {@link Settings} property or {@link UserManager}
* restriction changed, which includes the initial creation of the service.
*
* <p>Subclasses can extend this method to provide extra initialization, like clearing up
* previous state.
*
* @param disabled whether the service is disabled (due to {@link UserManager} restrictions).
*
* @return whether the disabled state changed.
*/
@GuardedBy("mLock")
@CallSuper
protected boolean updateLocked(boolean disabled) {
final boolean wasEnabled = isEnabledLocked();
if (mMaster.verbose) {
Slog.v(mTag, "updateLocked(u=" + mUserId + "): wasEnabled=" + wasEnabled
+ ", mSetupComplete=" + mSetupComplete
+ ", disabled=" + disabled + ", mDisabled=" + mDisabled);
}
updateIsSetupComplete(mUserId);
mDisabled = disabled;
updateServiceInfoLocked();
return wasEnabled != isEnabledLocked();
}
/**
* Updates the internal reference to the service info, and returns the service's component.
*/
protected final ComponentName updateServiceInfoLocked() {
ComponentName serviceComponent = null;
if (mMaster.mServiceNameResolver != null) {
ServiceInfo serviceInfo = null;
final String componentName = getComponentNameLocked();
if (!TextUtils.isEmpty(componentName)) {
try {
serviceComponent = ComponentName.unflattenFromString(componentName);
serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
0, mUserId);
if (serviceInfo == null) {
Slog.e(mTag, "Bad service name: " + componentName);
}
} catch (RuntimeException | RemoteException e) {
Slog.e(mTag, "Error getting service info for '" + componentName + "': " + e);
serviceInfo = null;
}
}
try {
if (serviceInfo != null) {
mServiceInfo = newServiceInfoLocked(serviceComponent);
if (mMaster.debug) {
Slog.d(mTag, "Set component for user " + mUserId + " as "
+ serviceComponent + " and info as " + mServiceInfo);
}
} else {
mServiceInfo = null;
if (mMaster.debug) {
Slog.d(mTag, "Reset component for user " + mUserId + ":" + componentName);
}
}
} catch (Exception e) {
Slog.e(mTag, "Bad ServiceInfo for '" + componentName + "': " + e);
mServiceInfo = null;
}
}
return serviceComponent;
}
/**
* Gets the user associated with this service.
*/
public final @UserIdInt int getUserId() {
return mUserId;
}
/**
* Gets the master service.
*/
public final M getMaster() {
return mMaster;
}
/**
* Gets this UID of the remote service this service binds to, or {@code -1} if the service is
* disabled.
*/
@GuardedBy("mLock")
protected final int getServiceUidLocked() {
if (mServiceInfo == null) {
if (mMaster.verbose) Slog.v(mTag, "getServiceUidLocked(): no mServiceInfo");
return Process.INVALID_UID;
}
return mServiceInfo.applicationInfo.uid;
}
/**
* Gets the current name of the service, which is either the default service or the
* {@link AbstractMasterSystemService#setTemporaryService(int, String, int) temporary one}.
*/
protected final @Nullable String getComponentNameLocked() {
return mMaster.mServiceNameResolver.getServiceName(mUserId);
}
/**
* Checks whether the current service for the user was temporarily set.
*/
public final boolean isTemporaryServiceSetLocked() {
return mMaster.mServiceNameResolver.isTemporary(mUserId);
}
/**
* Resets the temporary service implementation to the default component.
*/
protected final void resetTemporaryServiceLocked() {
mMaster.mServiceNameResolver.resetTemporaryService(mUserId);
}
/**
* Gets the {@link ServiceInfo} of the remote service this service binds to, or {@code null}
* if the service is disabled.
*/
@Nullable
public final ServiceInfo getServiceInfo() {
return mServiceInfo;
}
/**
* Gets the {@link ComponentName} of the remote service this service binds to, or {@code null}
* if the service is disabled.
*/
@Nullable
public final ComponentName getServiceComponentName() {
synchronized (mLock) {
return mServiceInfo == null ? null : mServiceInfo.getComponentName();
}
}
/**
* Gets the name of the of the app this service binds to, or {@code null} if the service is
* disabled.
*/
@Nullable
public final String getServicePackageName() {
final ComponentName serviceComponent = getServiceComponentName();
return serviceComponent == null ? null : serviceComponent.getPackageName();
}
/**
* Gets the user-visibile name of the service this service binds to, or {@code null} if the
* service is disabled.
*/
@Nullable
@GuardedBy("mLock")
public final CharSequence getServiceLabelLocked() {
return mServiceInfo == null ? null : mServiceInfo.loadSafeLabel(
getContext().getPackageManager(), 0 /* do not ellipsize */,
PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE | PackageItemInfo.SAFE_LABEL_FLAG_TRIM);
}
/**
* Gets the icon the service this service binds to, or {@code null} if the service is disabled.
*/
@Nullable
@GuardedBy("mLock")
public final Drawable getServiceIconLocked() {
return mServiceInfo == null ? null
: mServiceInfo.loadIcon(getContext().getPackageManager());
}
/**
* Removes the service from the master's cache.
*/
protected final void removeSelfFromCacheLocked() {
mMaster.removeCachedServiceLocked(mUserId);
}
/**
* Whether the service should log debug statements.
*/
//TODO(b/117779333): consider using constants for these guards
public final boolean isDebug() {
return mMaster.debug;
}
/**
* Whether the service should log verbose statements.
*/
//TODO(b/117779333): consider using constants for these guards
public final boolean isVerbose() {
return mMaster.verbose;
}
/**
* Gets the target SDK level of the service this service binds to,
* or {@code 0} if the service is disabled.
*/
public final int getTargedSdkLocked() {
return mServiceInfo == null ? 0 : mServiceInfo.applicationInfo.targetSdkVersion;
}
/**
* Gets whether the device already finished setup.
*/
protected final boolean isSetupCompletedLocked() {
return mSetupComplete;
}
/**
* Gets the context associated with this service.
*/
protected final Context getContext() {
return mMaster.getContext();
}
// TODO(b/117779333): support proto
@GuardedBy("mLock")
protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("User: "); pw.println(mUserId);
if (mServiceInfo != null) {
pw.print(prefix); pw.print("Service Label: "); pw.println(getServiceLabelLocked());
pw.print(prefix); pw.print("Target SDK: "); pw.println(getTargedSdkLocked());
}
if (mMaster.mServiceNameResolver != null) {
pw.print(prefix); pw.print("Name resolver: ");
mMaster.mServiceNameResolver.dumpShort(pw, mUserId); pw.println();
}
pw.print(prefix); pw.print("Disabled by UserManager: "); pw.println(mDisabled);
pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
if (mServiceInfo != null) {
pw.print(prefix); pw.print("Service UID: ");
pw.println(mServiceInfo.applicationInfo.uid);
}
pw.println();
}
}