blob: a26102d57297c39f446390958c714a09df3dc9b7 [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;
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.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import android.util.TimeUtils;
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>> {
/** Handler message to {@link #resetTemporaryServiceLocked()} */
private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
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;
/**
* Temporary service name set by {@link #setTemporaryServiceLocked(String, int)}.
*
* <p>Typically used by Shell command and/or CTS tests.
*/
@GuardedBy("mLock")
private String mTemporaryServiceName;
/**
* When the temporary service will expire (and reset back to the default).
*/
@GuardedBy("mLock")
private long mTemporaryServiceExpiration;
/**
* Handler used to reset the temporary service name.
*/
@GuardedBy("mLock")
private Handler mTemporaryHandler;
protected AbstractPerUserSystemService(@NonNull M master, @NonNull Object lock,
@UserIdInt int userId) {
mMaster = master;
mLock = lock;
mUserId = userId;
}
/**
* Creates a new {@link ServiceInfo} for the given service name.
*
* @throws NameNotFoundException if the service does not exist.
* @throws SecurityException if the service does not have the proper permissions to be bound to.
*/
protected abstract ServiceInfo newServiceInfo(@NonNull ComponentName serviceComponent)
throws NameNotFoundException;
/**
* 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;
}
/**
* 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);
}
mSetupComplete = isSetupCompletedLocked();
mDisabled = disabled;
ComponentName serviceComponent = 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 = newServiceInfo(serviceComponent);
if (mMaster.debug) {
Slog.d(mTag, "Set component for user " + mUserId + " 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 wasEnabled != isEnabledLocked();
}
/**
* 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
* {@link #getDefaultComponentName() default service} or the
* {@link #setTemporaryServiceLocked(String, int) temporary one}.
*/
protected final String getComponentNameLocked() {
if (mTemporaryServiceName != null) {
// Always log it, as it should only be used on CTS or during development
Slog.w(mTag, "getComponentName(): using temporary name " + mTemporaryServiceName);
return mTemporaryServiceName;
}
return getDefaultComponentName();
}
/**
* Gets the name of the default component for the service.
*
* <p>Typically implemented by returning {@link #getComponentNameFromSettings()} or by using
* a string from the system resources.
*/
@Nullable
protected abstract String getDefaultComponentName();
/**
* Gets this name of the remote service this service binds to as defined by {@link Settings}.
*/
@Nullable
protected final String getComponentNameFromSettings() {
final String property = mMaster.getServiceSettingsProperty();
return property == null ? null : Settings.Secure
.getStringForUser(getContext().getContentResolver(), property, mUserId);
}
/**
* Checks whether the current service for the user was temporarily set.
*/
public final boolean isTemporaryServiceSetLocked() {
return mTemporaryServiceName != null;
}
/**
* Temporary sets the service implementation.
*
* @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).
*/
protected final void setTemporaryServiceLocked(@NonNull String componentName, int durationMs) {
mTemporaryServiceName = componentName;
if (mTemporaryHandler == null) {
mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
synchronized (mLock) {
resetTemporaryServiceLocked();
}
} else {
Slog.wtf(mTag, "invalid handler msg: " + msg);
}
}
};
} else {
removeResetTemporaryServiceMessageLocked();
}
mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
updateLocked(mDisabled);
}
private void removeResetTemporaryServiceMessageLocked() {
if (mMaster.verbose) {
Slog.v(mTag, "setTemporaryServiceLocked(): removing old message");
}
// NOTE: caller should already have checked it
mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
}
/**
* Resets the temporary service implementation to the default component.
*/
protected final void resetTemporaryServiceLocked() {
Slog.i(mTag, "resetting temporary service from " + mTemporaryServiceName);
mTemporaryServiceName = null;
if (mTemporaryHandler != null) {
removeResetTemporaryServiceMessageLocked();
mTemporaryHandler = null;
}
updateLocked(mDisabled);
}
/**
* 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());
}
/**
* Whether the service should log debug statements.
*/
public final boolean isDebug() {
return mMaster.debug;
}
/**
* Whether the service should log verbose statements.
*/
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() {
final String setupComplete = Settings.Secure.getStringForUser(
getContext().getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, mUserId);
return "1".equals(setupComplete);
}
/**
* 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);
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);
}
if (mTemporaryServiceName != null) {
pw.print(prefix); pw.print("Temporary service name: "); pw.print(mTemporaryServiceName);
final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
pw.print(" (expires in "); TimeUtils.formatDuration(ttl, pw); pw.println(")");
pw.print(prefix); pw.print(prefix);
pw.print("Default service name: "); pw.println(getDefaultComponentName());
} else {
pw.print(prefix); pw.print("Service name: "); pw.println(getDefaultComponentName());
}
}
}