blob: 8e823168bf7adeed422030fd07e906dd04ce8abc [file] [log] [blame]
/*
* Copyright (C) 2017 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.internal.car;
import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT;
import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
import android.app.admin.DevicePolicyManager.OperationSafetyReason;
import android.app.admin.DevicePolicySafetyChecker;
import android.automotive.watchdog.internal.ICarWatchdogMonitor;
import android.automotive.watchdog.internal.PowerCycle;
import android.automotive.watchdog.internal.StateType;
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.UserInfo;
import android.hidl.manager.V1_0.IServiceManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.TimeUtils;
import com.android.car.internal.ICarServiceHelper;
import com.android.car.internal.ICarSystemServerClient;
import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
import com.android.car.internal.common.EventLogTags;
import com.android.car.internal.common.UserHelperLite;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.server.Dumpable;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.CarLaunchParamsModifier;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* System service side companion service for CarService. Starts car service and provide necessary
* API for CarService. Only for car product.
*/
public class CarServiceHelperService extends SystemService
implements Dumpable, DevicePolicySafetyChecker {
private static final String TAG = "CarServiceHelper";
// TODO(b/154033860): STOPSHIP if they're still true
private static final boolean DBG = true;
private static final boolean VERBOSE = true;
private static final String PROP_RESTART_RUNTIME = "ro.car.recovery.restart_runtime.enabled";
private static final List<String> CAR_HAL_INTERFACES_OF_INTEREST = Arrays.asList(
"android.hardware.automotive.vehicle@2.0::IVehicle",
"android.hardware.automotive.audiocontrol@1.0::IAudioControl",
"android.hardware.automotive.audiocontrol@2.0::IAudioControl"
);
// Message ID representing post-processing of process dumping.
private static final int WHAT_POST_PROCESS_DUMPING = 1;
// Message ID representing process killing.
private static final int WHAT_PROCESS_KILL = 2;
// Message ID representing service unresponsiveness.
private static final int WHAT_SERVICE_UNRESPONSIVE = 3;
private static final long CAR_SERVICE_BINDER_CALL_TIMEOUT = 15_000;
private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0;
private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();
private final Context mContext;
private final Object mLock = new Object();
@GuardedBy("mLock")
private IBinder mCarServiceBinder;
@GuardedBy("mLock")
private boolean mSystemBootCompleted;
private final CarLaunchParamsModifier mCarLaunchParamsModifier;
private final Handler mHandler;
private final HandlerThread mHandlerThread = new HandlerThread("CarServiceHelperService");
private final ProcessTerminator mProcessTerminator = new ProcessTerminator();
private final CarServiceConnectedCallback mCarServiceConnectedCallback =
new CarServiceConnectedCallback();
private final CarServiceProxy mCarServiceProxy;
/**
* End-to-end time (from process start) for unlocking the first non-system user.
*/
private long mFirstUnlockedUserDuration;
private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
private final ICarWatchdogMonitorImpl mCarWatchdogMonitor = new ICarWatchdogMonitorImpl(this);
private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener =
(connected) -> {
if (connected) {
registerMonitorToWatchdogDaemon();
}
};
private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
if (DBG) {
Slogf.d(TAG, "onServiceConnected: %s", iBinder);
}
handleCarServiceConnection(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
handleCarServiceCrash();
}
};
private final BroadcastReceiver mShutdownEventReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Skip immediately if intent is not relevant to device shutdown.
// FLAG_RECEIVER_FOREGROUND is checked to ignore the intent from UserController when
// a user is stopped.
if ((!intent.getAction().equals(Intent.ACTION_REBOOT)
&& !intent.getAction().equals(Intent.ACTION_SHUTDOWN))
|| (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) == 0) {
return;
}
int powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
powerCycle, /* arg2= */ 0);
if (DBG) {
Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
}
} catch (RemoteException | RuntimeException e) {
Slogf.w(TAG, "Notifying power cycle state change failed: %s", e);
}
}
};
private final CarDevicePolicySafetyChecker mCarDevicePolicySafetyChecker;
public CarServiceHelperService(Context context) {
this(context,
new CarLaunchParamsModifier(context),
new CarWatchdogDaemonHelper(TAG),
null
);
}
@VisibleForTesting
CarServiceHelperService(
Context context,
CarLaunchParamsModifier carLaunchParamsModifier,
CarWatchdogDaemonHelper carWatchdogDaemonHelper,
CarServiceProxy carServiceOperationManager) {
super(context);
mContext = context;
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mCarLaunchParamsModifier = carLaunchParamsModifier;
mCarWatchdogDaemonHelper = carWatchdogDaemonHelper;
mCarServiceProxy =
carServiceOperationManager == null ? new CarServiceProxy(this)
: carServiceOperationManager;
UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
if (umi != null) {
umi.addUserLifecycleListener(new UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, Object token) {
if (DBG) Slogf.d(TAG, "onUserCreated(): %s", user.toFullString());
}
@Override
public void onUserRemoved(UserInfo user) {
if (DBG) Slogf.d(TAG, "onUserRemoved(): $s", user.toFullString());
mCarServiceProxy.onUserRemoved(user);
}
});
} else {
Slogf.e(TAG, "UserManagerInternal not available - should only happen on unit tests");
}
mCarDevicePolicySafetyChecker = new CarDevicePolicySafetyChecker(this);
}
@Override
public void onBootPhase(int phase) {
EventLog.writeEvent(EventLogTags.CAR_HELPER_BOOT_PHASE, phase);
if (DBG) Slogf.d(TAG, "onBootPhase: %d", phase);
TimingsTraceAndSlog t = newTimingsTraceAndSlog();
if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
t.traceBegin("onBootPhase.3pApps");
mCarLaunchParamsModifier.init();
setupAndStartUsers(t);
t.traceEnd();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
t.traceBegin("onBootPhase.completed");
synchronized (mLock) {
mSystemBootCompleted = true;
}
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(
StateType.BOOT_PHASE, phase, /* arg2= */ 0);
} catch (RemoteException | RuntimeException e) {
Slogf.w(TAG, "Failed to notify boot phase change: %s", e);
}
t.traceEnd();
}
}
@Override
public void onStart() {
EventLog.writeEvent(EventLogTags.CAR_HELPER_START);
IntentFilter filter = new IntentFilter(Intent.ACTION_REBOOT);
filter.addAction(Intent.ACTION_SHUTDOWN);
mContext.registerReceiverForAllUsers(mShutdownEventReceiver, filter, null, null);
mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
mCarWatchdogDaemonHelper.connect();
Intent intent = new Intent();
intent.setPackage("com.android.car");
intent.setAction(CAR_SERVICE_INTERFACE);
if (!mContext.bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
mHandler, UserHandle.SYSTEM)) {
Slogf.wtf(TAG, "cannot start car service");
}
loadNativeLibrary();
}
@Override
public void dump(IndentingPrintWriter pw, String[] args) {
if (args == null || args.length == 0 || args[0].equals("-a")) {
pw.printf("System boot completed: %b\n", mSystemBootCompleted);
pw.print("First unlocked user duration: ");
TimeUtils.formatDuration(mFirstUnlockedUserDuration, pw); pw.println();
pw.printf("Queued tasks: %d\n", mProcessTerminator.mQueuedTask);
mCarServiceProxy.dump(pw);
mCarDevicePolicySafetyChecker.dump(pw);
return;
}
if ("--user-metrics-only".equals(args[0])) {
mCarServiceProxy.dumpUserMetrics(pw);
return;
}
if ("--is-operation-safe".equals(args[0]) & args.length > 1) {
String arg1 = args[1];
int operation = 0;
try {
operation = Integer.parseInt(arg1);
} catch (Exception e) {
pw.printf("Invalid operation type: %s\n", arg1);
return;
}
int reason = getUnsafeOperationReason(operation);
boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
pw.printf("Operation %s is %s. Reason: %s\n",
DevicePolicyManager.operationToString(operation),
safe ? "SAFE" : "UNSAFE",
DevicePolicyManager.operationSafetyReasonToString(reason));
return;
}
pw.printf("Invalid args: %s\n", Arrays.toString(args));
}
@Override
public String getDumpableName() {
return "CarServiceHelper";
}
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKING, user.getUserIdentifier());
if (DBG) Slogf.d(TAG, "onUserUnlocking(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, user);
}
@Override
public void onUserUnlocked(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)) return;
int userId = user.getUserIdentifier();
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKED, userId);
if (DBG) Slogf.d(TAG, "onUserUnlocked(%s)", user);
if (mFirstUnlockedUserDuration == 0 && !UserHelperLite.isHeadlessSystemUser(userId)) {
mFirstUnlockedUserDuration = SystemClock.elapsedRealtime()
- Process.getStartElapsedRealtime();
Slogf.i(TAG, "Time to unlock 1st user(%s): %s", user,
TimeUtils.formatDuration(mFirstUnlockedUserDuration));
}
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, user);
}
@Override
public void onUserStarting(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STARTING)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STARTING, user.getUserIdentifier());
if (DBG) Slogf.d(TAG, "onUserStarting(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, user);
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPING)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPING, user.getUserIdentifier());
if (DBG) Slogf.d(TAG, "onUserStopping(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, user);
int userId = user.getUserIdentifier();
mCarLaunchParamsModifier.handleUserStopped(userId);
}
@Override
public void onUserStopped(@NonNull TargetUser user) {
if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPED, user.getUserIdentifier());
if (DBG) Slogf.d(TAG, "onUserStopped(%s)", user);
sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, user);
}
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
if (isPreCreated(to, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) return;
EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_SWITCHING,
from == null ? UserHandle.USER_NULL : from.getUserIdentifier(),
to.getUserIdentifier());
if (DBG) Slogf.d(TAG, "onUserSwitching(%s>>%s)", from, to);
mCarServiceProxy.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
from, to);
int userId = to.getUserIdentifier();
mCarLaunchParamsModifier.handleCurrentUserSwitching(userId);
}
@Override // from DevicePolicySafetyChecker
@OperationSafetyReason
public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
return mCarDevicePolicySafetyChecker.isDevicePolicyOperationSafe(operation)
? DevicePolicyManager.OPERATION_SAFETY_REASON_NONE
: DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
}
@Override // from DevicePolicySafetyChecker
public boolean isSafeOperation(@OperationSafetyReason int reason) {
return mCarDevicePolicySafetyChecker.isSafe();
}
@Override // from DevicePolicySafetyChecker
public void onFactoryReset(IResultReceiver callback) {
if (DBG) Slogf.d(TAG, "onFactoryReset: %s", callback);
mCarServiceProxy.onFactoryReset(callback);
}
@VisibleForTesting
void loadNativeLibrary() {
System.loadLibrary("car-framework-service-jni");
}
private boolean isPreCreated(@NonNull TargetUser user, @UserLifecycleEventType int eventType) {
if (!user.isPreCreated()) return false;
if (DBG) {
Slogf.d(TAG, "Ignoring event of type %d for pre-created user %s", eventType, user);
}
return true;
}
@VisibleForTesting
void handleCarServiceConnection(IBinder iBinder) {
synchronized (mLock) {
if (mCarServiceBinder == iBinder) {
return; // already connected.
}
Slogf.i(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, iBinder);
mCarServiceBinder = iBinder;
Slogf.i(TAG, "**CarService connected**");
}
sendSetSystemServerConnectionsCall();
mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
mHandler.sendMessageDelayed(
obtainMessage(CarServiceHelperService::handleCarServiceUnresponsive, this)
.setWhat(WHAT_SERVICE_UNRESPONSIVE), CAR_SERVICE_BINDER_CALL_TIMEOUT);
}
private TimingsTraceAndSlog newTimingsTraceAndSlog() {
return new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
}
private void setupAndStartUsers(@NonNull TimingsTraceAndSlog t) {
// TODO(b/156263735): decide if it should return in case the device's on Retail Mode
t.traceBegin("setupAndStartUsers");
mCarServiceProxy.initBootUser();
t.traceEnd();
}
private void handleCarServiceUnresponsive() {
// This should not happen. Calling this method means ICarSystemServerClient binder is not
// returned after service connection. and CarService has not connected in the given time.
Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService unresponsive.");
Slogf.w(TAG, "*** GOODBYE!");
Process.killProcess(Process.myPid());
System.exit(10);
}
private void sendSetSystemServerConnectionsCall() {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
data.writeStrongBinder(mHelper.asBinder());
data.writeStrongBinder(mCarServiceConnectedCallback.asBinder());
IBinder binder;
synchronized (mLock) {
binder = mCarServiceBinder;
}
int code = IBinder.FIRST_CALL_TRANSACTION;
try {
if (VERBOSE) Slogf.v(TAG, "calling one-way binder transaction with code %d", code);
// oneway void setSystemServerConnections(in IBinder helper, in IBinder receiver) = 0;
binder.transact(code, data, null, Binder.FLAG_ONEWAY);
if (VERBOSE) Slogf.v(TAG, "finished one-way binder transaction with code %d", code);
} catch (RemoteException e) {
Slogf.w(TAG, "RemoteException from car service", e);
handleCarServiceCrash();
} catch (RuntimeException e) {
Slogf.wtf(TAG, e, "Exception calling binder transaction (real code: %d)", code);
throw e;
} finally {
data.recycle();
}
}
private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType,
@NonNull TargetUser user) {
mCarServiceProxy.sendUserLifecycleEvent(eventType, /* from= */ null, user);
}
// Adapted from frameworks/base/services/core/java/com/android/server/Watchdog.java
// TODO(b/131861630) use implementation common with Watchdog.java
//
private static ArrayList<Integer> getInterestingHalPids() {
try {
IServiceManager serviceManager = IServiceManager.getService();
ArrayList<IServiceManager.InstanceDebugInfo> dump =
serviceManager.debugDump();
HashSet<Integer> pids = new HashSet<>();
for (IServiceManager.InstanceDebugInfo info : dump) {
if (info.pid == IServiceManager.PidConstant.NO_PID) {
continue;
}
if (Watchdog.HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName) ||
CAR_HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
pids.add(info.pid);
}
}
return new ArrayList<Integer>(pids);
} catch (RemoteException e) {
return new ArrayList<Integer>();
}
}
// Adapted from frameworks/base/services/core/java/com/android/server/Watchdog.java
// TODO(b/131861630) use implementation common with Watchdog.java
//
private static ArrayList<Integer> getInterestingNativePids() {
ArrayList<Integer> pids = getInterestingHalPids();
int[] nativePids = Process.getPidsForCommands(Watchdog.NATIVE_STACKS_OF_INTEREST);
if (nativePids != null) {
pids.ensureCapacity(pids.size() + nativePids.length);
for (int i : nativePids) {
pids.add(i);
}
}
return pids;
}
// Borrowed from Watchdog.java. Create an ANR file from the call stacks.
//
private static void dumpServiceStacks() {
ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(
pids, null, null, getInterestingNativePids(), null);
}
@VisibleForTesting
void handleCarServiceCrash() {
// Recovery behavior. Kill the system server and reset
// everything if enabled by the property.
boolean restartOnServiceCrash = SystemProperties.getBoolean(PROP_RESTART_RUNTIME, false);
mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
dumpServiceStacks();
if (restartOnServiceCrash) {
Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService crash");
Slogf.w(TAG, "*** GOODBYE!");
Process.killProcess(Process.myPid());
System.exit(10);
} else {
Slogf.w(TAG, "*** CARHELPER ignoring: CarService crash");
}
}
private void handleClientsNotResponding(@NonNull int[] pids) {
mProcessTerminator.requestTerminateProcess(pids);
}
private void registerMonitorToWatchdogDaemon() {
try {
mCarWatchdogDaemonHelper.registerMonitor(mCarWatchdogMonitor);
} catch (RemoteException | RuntimeException e) {
Slogf.w(TAG, "Cannot register to car watchdog daemon: %s", e);
}
}
private void killProcessAndReportToMonitor(int pid) {
String processName = getProcessName(pid);
Process.killProcess(pid);
Slogf.w(TAG, "carwatchdog killed %s (pid: %d)", processName, pid);
try {
mCarWatchdogDaemonHelper.tellDumpFinished(mCarWatchdogMonitor, pid);
} catch (RemoteException | RuntimeException e) {
Slogf.w(TAG, "Cannot report monitor result to car watchdog daemon: %s", e);
}
}
private static String getProcessName(int pid) {
String unknownProcessName = "unknown process";
String filename = "/proc/" + pid + "/cmdline";
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line = reader.readLine().replace('\0', ' ').trim();
int index = line.indexOf(' ');
if (index != -1) {
line = line.substring(0, index);
}
return Paths.get(line).getFileName().toString();
} catch (IOException e) {
Slogf.w(TAG, "Cannot read %s", filename);
return unknownProcessName;
}
}
private static native int nativeForceSuspend(int timeoutMs);
// TODO(b/173664653): it's missing unit tests (for example, to make sure that
// when its setSafetyMode() is called, mCarDevicePolicySafetyChecker is updated).
private class ICarServiceHelperImpl extends ICarServiceHelper.Stub {
/**
* Force device to suspend
*/
@Override // Binder call
public int forceSuspend(int timeoutMs) {
int retVal;
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
final long ident = Binder.clearCallingIdentity();
try {
retVal = nativeForceSuspend(timeoutMs);
} finally {
Binder.restoreCallingIdentity(ident);
}
return retVal;
}
@Override
public void setDisplayAllowlistForUser(@UserIdInt int userId, int[] displayIds) {
mCarLaunchParamsModifier.setDisplayAllowListForUser(userId, displayIds);
}
@Override
public void setPassengerDisplays(int[] displayIdsForPassenger) {
mCarLaunchParamsModifier.setPassengerDisplays(displayIdsForPassenger);
}
@Override
public void setSourcePreferredComponents(boolean enableSourcePreferred,
@Nullable List<ComponentName> sourcePreferredComponents) {
mCarLaunchParamsModifier.setSourcePreferredComponents(
enableSourcePreferred, sourcePreferredComponents);
}
@Override
public void setSafetyMode(boolean safe) {
mCarDevicePolicySafetyChecker.setSafe(safe);
}
@Override
public UserInfo createUserEvenWhenDisallowed(String name, String userType, int flags) {
if (DBG) {
Slogf.d(TAG, "createUserEvenWhenDisallowed(): name=%s, type=%s, flags=%s",
UserHelperLite.safeName(name), userType, UserInfo.flagsToString(flags));
}
UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
try {
UserInfo user = umi.createUserEvenWhenDisallowed(name, userType, flags,
/* disallowedPackages= */ null, /* token= */ null);
if (DBG) {
Slogf.d(TAG, "User created: %s", (user == null ? "null" : user.toFullString()));
}
// TODO(b/172691310): decide if user should be affiliated when DeviceOwner is set
return user;
} catch (UserManager.CheckedUserOperationException e) {
Slogf.e(TAG, "Error creating user", e);
return null;
}
}
}
private class ICarWatchdogMonitorImpl extends ICarWatchdogMonitor.Stub {
private final WeakReference<CarServiceHelperService> mService;
private ICarWatchdogMonitorImpl(CarServiceHelperService service) {
mService = new WeakReference<>(service);
}
@Override
public void onClientsNotResponding(int[] pids) {
CarServiceHelperService service = mService.get();
if (service == null || pids == null || pids.length == 0) {
return;
}
service.handleClientsNotResponding(pids);
}
}
private final class ProcessTerminator {
private static final long ONE_SECOND_MS = 1_000L;
private final Object mProcessLock = new Object();
private ExecutorService mExecutor;
@GuardedBy("mProcessLock")
private int mQueuedTask;
public void requestTerminateProcess(@NonNull int[] pids) {
synchronized (mProcessLock) {
// If there is a running thread, we re-use it instead of starting a new thread.
if (mExecutor == null) {
mExecutor = Executors.newSingleThreadExecutor();
}
mQueuedTask++;
}
mExecutor.execute(() -> {
for (int pid : pids) {
dumpAndKillProcess(pid);
}
// mExecutor will be stopped from the main thread, if there is no queued task.
mHandler.sendMessage(obtainMessage(ProcessTerminator::postProcessing, this)
.setWhat(WHAT_POST_PROCESS_DUMPING));
});
}
private void postProcessing() {
synchronized (mProcessLock) {
mQueuedTask--;
if (mQueuedTask == 0) {
mExecutor.shutdown();
mExecutor = null;
}
}
}
private void dumpAndKillProcess(int pid) {
if (DBG) {
Slogf.d(TAG, "Dumping and killing process(pid: %d)", pid);
}
ArrayList<Integer> javaPids = new ArrayList<>(1);
ArrayList<Integer> nativePids = new ArrayList<>();
try {
if (isJavaApp(pid)) {
javaPids.add(pid);
} else {
nativePids.add(pid);
}
} catch (IOException e) {
Slogf.w(TAG, "Cannot get process information: %s", e);
return;
}
nativePids.addAll(getInterestingNativePids());
long startDumpTime = SystemClock.uptimeMillis();
ActivityManagerService.dumpStackTraces(javaPids, null, null, nativePids, null);
long dumpTime = SystemClock.uptimeMillis() - startDumpTime;
if (DBG) {
Slogf.d(TAG, "Dumping process took %dms", dumpTime);
}
// To give clients a chance of wrapping up before the termination.
if (dumpTime < ONE_SECOND_MS) {
mHandler.sendMessageDelayed(obtainMessage(
CarServiceHelperService::killProcessAndReportToMonitor,
CarServiceHelperService.this, pid).setWhat(WHAT_PROCESS_KILL),
ONE_SECOND_MS - dumpTime);
} else {
killProcessAndReportToMonitor(pid);
}
}
private boolean isJavaApp(int pid) throws IOException {
Path exePath = new File("/proc/" + pid + "/exe").toPath();
String target = Files.readSymbolicLink(exePath).toString();
// Zygote's target exe is also /system/bin/app_process32 or /system/bin/app_process64.
// But, we can be very sure that Zygote will not be the client of car watchdog daemon.
return target == "/system/bin/app_process32" || target == "/system/bin/app_process64";
}
}
private final class CarServiceConnectedCallback extends IResultReceiver.Stub {
@Override
public void send(int resultCode, Bundle resultData) {
mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
IBinder binder;
if (resultData == null
|| (binder = resultData.getBinder(ICAR_SYSTEM_SERVER_CLIENT)) == null) {
Slogf.wtf(TAG, "setSystemServerConnections return NULL Binder.");
handleCarServiceUnresponsive();
return;
}
ICarSystemServerClient carService = ICarSystemServerClient.Stub.asInterface(binder);
mCarServiceProxy.handleCarServiceConnection(carService);
}
}
}