blob: 5772e198a01a911e08dc408549ea222e18766c39 [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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.car.userlib.CarUserManagerHelper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.UserInfo;
import android.hidl.manager.V1_0.IServiceManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.sysprop.CarProperties;
import android.util.Slog;
import android.util.TimingsTraceLog;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* 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 {
// Place holder for user name of the first user created.
private static final String TAG = "CarServiceHelper";
private static final boolean DBG = true;
private static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
// These numbers should match with binder call order of
// packages/services/Car/car-lib/src/android/car/ICar.aidl
private static final int ICAR_CALL_SET_CAR_SERVICE_HELPER = 0;
private static final int ICAR_CALL_SET_USER_UNLOCK_STATUS = 1;
private static final int ICAR_CALL_SET_SWITCH_USER = 2;
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"
);
@GuardedBy("mLock")
private int mLastSwitchedUser = UserHandle.USER_NULL;
private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();
private final Context mContext;
private final Object mLock = new Object();
@GuardedBy("mLock")
private IBinder mCarService;
@GuardedBy("mLock")
private boolean mSystemBootCompleted;
@GuardedBy("mLock")
private final HashMap<Integer, Boolean> mUserUnlockedStatus = new HashMap<>();
private final CarUserManagerHelper mCarUserManagerHelper;
private final UserManager mUserManager;
private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
if (DBG) {
Slog.d(TAG, "onServiceConnected:" + iBinder);
}
handleCarServiceConnection(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
handleCarServiceCrash();
}
};
public CarServiceHelperService(Context context) {
this(context, new CarUserManagerHelper(context));
}
@VisibleForTesting
CarServiceHelperService(Context context, CarUserManagerHelper carUserManagerHelper) {
super(context);
mContext = context;
mCarUserManagerHelper = carUserManagerHelper;
mUserManager = UserManager.get(context);
}
@Override
public void onBootPhase(int phase) {
if (DBG) {
Slog.d(TAG, "onBootPhase:" + phase);
}
if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
checkForCarServiceConnection();
// TODO(b/126199560) Consider doing this earlier in onStart().
// Other than onStart, PHASE_THIRD_PARTY_APPS_CAN_START is the earliest timing.
setupAndStartUsers();
checkForCarServiceConnection();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
TimingsTraceLog t = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
t.traceBegin("onBootPhase.completed");
managePreCreatedUsers();
boolean shouldNotify = false;
synchronized (mLock) {
mSystemBootCompleted = true;
if (mCarService != null) {
shouldNotify = true;
}
}
if (shouldNotify) {
notifyAllUnlockedUsers();
}
t.traceEnd();
}
}
@Override
public void onStart() {
Intent intent = new Intent();
intent.setPackage("com.android.car");
intent.setAction(CAR_SERVICE_INTERFACE);
if (!getContext().bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
UserHandle.SYSTEM)) {
Slog.wtf(TAG, "cannot start car service");
}
System.loadLibrary("car-framework-service-jni");
}
@Override
public void onUnlockUser(int userHandle) {
handleUserLockStatusChange(userHandle, true);
if (DBG) {
Slog.d(TAG, "User" + userHandle + " unlocked");
}
}
@Override
public void onStopUser(int userHandle) {
handleUserLockStatusChange(userHandle, false);
}
@Override
public void onCleanupUser(int userHandle) {
handleUserLockStatusChange(userHandle, false);
}
@Override
public void onSwitchUser(int userHandle) {
synchronized (mLock) {
mLastSwitchedUser = userHandle;
if (mCarService == null) {
return; // The event will be delivered upon CarService connection.
}
}
sendSwitchUserBindercall(userHandle);
}
// Sometimes car service onConnected call is delayed a lot. car service binder can be
// found from ServiceManager directly. So do some polling during boot-up to connect to
// car service ASAP.
private void checkForCarServiceConnection() {
synchronized (mLock) {
if (mCarService != null) {
return;
}
}
IBinder iBinder = ServiceManager.checkService("car_service");
if (iBinder != null) {
if (DBG) {
Slog.d(TAG, "Car service found through ServiceManager:" + iBinder);
}
handleCarServiceConnection(iBinder);
}
}
private void handleCarServiceConnection(IBinder iBinder) {
int lastSwitchedUser;
boolean systemBootCompleted;
synchronized (mLock) {
if (mCarService == iBinder) {
return; // already connected.
}
if (mCarService != null) {
Slog.i(TAG, "car service binder changed, was:" + mCarService
+ " new:" + iBinder);
}
mCarService = iBinder;
lastSwitchedUser = mLastSwitchedUser;
systemBootCompleted = mSystemBootCompleted;
}
Slog.i(TAG, "**CarService connected**");
sendSetCarServiceHelperBinderCall();
if (systemBootCompleted) {
notifyAllUnlockedUsers();
}
if (lastSwitchedUser != UserHandle.USER_NULL) {
sendSwitchUserBindercall(lastSwitchedUser);
}
}
private void handleUserLockStatusChange(int userHandle, boolean unlocked) {
boolean shouldNotify = false;
synchronized (mLock) {
Boolean oldStatus = mUserUnlockedStatus.get(userHandle);
if (oldStatus == null || oldStatus != unlocked) {
mUserUnlockedStatus.put(userHandle, unlocked);
if (mCarService != null && mSystemBootCompleted) {
shouldNotify = true;
}
}
}
if (shouldNotify) {
sendSetUserLockStatusBinderCall(userHandle, unlocked);
}
}
private void setupAndStartUsers() {
DevicePolicyManager devicePolicyManager =
mContext.getSystemService(DevicePolicyManager.class);
if (devicePolicyManager != null && devicePolicyManager.getUserProvisioningState()
!= DevicePolicyManager.STATE_USER_UNMANAGED) {
Slog.i(TAG, "DevicePolicyManager active, skip user unlock/switch");
return;
}
// Offloading the whole unlock into separate thread did not help due to single locks
// used in AMS / PMS ended up stopping the world with lots of lock contention.
// To run these in background, there should be some improvements there.
int targetUserId = UserHandle.USER_SYSTEM;
if (mCarUserManagerHelper.getAllUsers().size() == 0) {
Slog.i(TAG, "Create new admin user and switch");
// On very first boot, create an admin user and switch to that user.
UserInfo admin = mCarUserManagerHelper.createNewAdminUser();
if (admin == null) {
Slog.e(TAG, "cannot create admin user");
return;
}
targetUserId = admin.id;
} else {
targetUserId = mCarUserManagerHelper.getInitialUser();
Slog.i(TAG, "Switching to user " + targetUserId + " on boot");
}
IActivityManager am = ActivityManager.getService();
if (am == null) {
Slog.wtf(TAG, "cannot get ActivityManagerService");
return;
}
// If system user is the only user to unlock, handle it when system completes the boot.
if (targetUserId == UserHandle.USER_SYSTEM) {
return;
}
TimingsTraceLog t = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
unlockSystemUser(t, am);
t.traceBegin("ForegroundUserStart" + targetUserId);
try {
if (!am.startUserInForegroundWithListener(targetUserId, null)) {
Slog.e(TAG, "cannot start foreground user:" + targetUserId);
} else {
mCarUserManagerHelper.setLastActiveUser(targetUserId);
}
} catch (RemoteException e) {
// should not happen for local call.
Slog.wtf("RemoteException from AMS", e);
}
t.traceEnd();
}
private void unlockSystemUser(@NonNull TimingsTraceLog t, @NonNull IActivityManager am) {
t.traceBegin("UnlockSystemUser");
try {
// This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not
// update the state and user 0 unlock happens twice.
boolean started = am.startUserInBackground(UserHandle.USER_SYSTEM);
if (!started) {
Slog.w(TAG, "could not restart system user in foreground; trying unlock instead");
t.traceBegin("forceUnlockSystemUser");
boolean unlocked = am.unlockUser(UserHandle.USER_SYSTEM,
/* token= */ null, /* secret= */ null, /* listner= */ null);
t.traceEnd();
if (!unlocked) {
Slog.w(TAG, "could not unlock system user neither");
return;
}
}
} catch (RemoteException e) {
// should not happen for local call.
Slog.wtf("RemoteException from AMS", e);
} finally {
t.traceEnd();
}
}
private void managePreCreatedUsers() {
// First gets how many pre-createad users are defined by the OEM
int numberRequestedGuests = CarProperties.number_pre_created_guests().orElse(0);
int numberRequestedUsers = CarProperties.number_pre_created_users().orElse(0);
Slog.i(TAG, "managePreCreatedUsers(): OEM asked for " + numberRequestedGuests
+ " guests and " + numberRequestedUsers + " users");
if (numberRequestedGuests < 0 || numberRequestedUsers < 0) {
Slog.w(TAG, "preCreateUsers(): invalid values provided by OEM; "
+ "number_pre_created_guests=" + numberRequestedGuests
+ ", number_pre_created_users=" + numberRequestedUsers);
return;
}
if (numberRequestedGuests == 0 && numberRequestedUsers == 0) {
Slog.i(TAG, "managePreCreatedUsers(): not defined by OEM");
return;
}
// Then checks how many exist already
List<UserInfo> allUsers = mUserManager.getUsers(/* excludePartial= */ true,
/* excludeDying= */ true, /* excludePreCreated= */ false);
int allUsersSize = allUsers.size();
if (DBG) Slog.d(TAG, "preCreateUsers: total users size is " + allUsersSize);
int numberExistingGuests = 0;
int numberExistingUsers = 0;
// List of pre-created users that were not properly initialized. Typically happens when
// the system crashed / rebooted before they were fully started.
SparseBooleanArray invalidUsers = new SparseBooleanArray();
for (int i = 0; i < allUsersSize; i++) {
UserInfo user = allUsers.get(i);
if (!user.preCreated) continue;
if (!user.isInitialized()) {
Slog.w(TAG, "Found invalid pre-created user that needs to be removed: "
+ user.toFullString());
invalidUsers.append(user.id, /* notUsed=*/ true);
continue;
}
if (user.isGuest()) {
numberExistingGuests++;
} else {
numberExistingUsers++;
}
}
if (DBG) {
Slog.i(TAG, "managePreCreatedUsers(): system already has " + numberExistingGuests
+ " pre-created guests," + numberExistingUsers + " pre-created users, and these"
+ " invalid users: " + invalidUsers );
}
int numberGuests = numberRequestedGuests - numberExistingGuests;
int numberUsers = numberRequestedUsers - numberExistingUsers;
int numberInvalidUsers = invalidUsers.size();
if (numberGuests <= 0 && numberUsers <= 0 && numberInvalidUsers == 0) {
Slog.i(TAG, "managePreCreatedUsers(): all pre-created and no invalid ones");
return;
}
// Finally, manage them....
// In theory, we could submit multiple user pre-creations in parallel, but we're
// submitting just 1 task, for 2 reasons:
// 1.To minimize it's effect on other system server initialization tasks.
// 2.The pre-created users will be unlocked in parallel anyways.
new Thread( () -> {
TimingsTraceLog t = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
t.traceBegin("preCreateUsers");
if (numberUsers > 0) {
preCreateUsers(t, numberUsers, /* isGuest= */ false);
}
if (numberGuests > 0) {
preCreateUsers(t, numberGuests, /* isGuest= */ true);
}
t.traceEnd();
if (numberInvalidUsers > 0) {
t.traceBegin("removeInvalidPreCreatedUsers");
for (int i = 0; i < numberInvalidUsers; i++) {
int userId = invalidUsers.keyAt(i);
Slog.i(TAG, "removing invalid pre-created user " + userId);
mUserManager.removeUser(userId);
}
t.traceEnd();
}
}, "CarServiceHelperManagePreCreatedUsers").start();
}
private void preCreateUsers(@NonNull TimingsTraceLog t, int size, boolean isGuest) {
String msg = isGuest ? "preCreateGuests-" + size : "preCreateUsers-" + size;
t.traceBegin(msg);
for (int i = 1; i <= size; i++) {
UserInfo preCreated = preCreateUsers(t, isGuest);
if (preCreated == null) {
Slog.w(TAG, "Could not pre-create " + (isGuest ? " guest " : "")
+ " user #" + i);
continue;
}
}
t.traceEnd();
}
// TODO(b/111451156): add unit test?
@Nullable
public UserInfo preCreateUsers(@NonNull TimingsTraceLog t, boolean isGuest) {
int flags = 0;
String traceMsg = "pre-create";
if (isGuest) {
flags |= UserInfo.FLAG_GUEST;
traceMsg += "-guest";
} else {
traceMsg += "-user";
}
t.traceBegin(traceMsg);
// NOTE: we want to get rid of UserManagerHelper, so let's call UserManager directly
UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
UserInfo user = um.preCreateUser(flags);
try {
if (user == null) {
// Couldn't create user, most likely because there are too many.
Slog.w(TAG, "couldn't " + traceMsg);
return null;
}
} finally {
t.traceEnd();
}
return user;
}
private void notifyAllUnlockedUsers() {
// only care about unlocked users
LinkedList<Integer> users = new LinkedList<>();
synchronized (mLock) {
for (Map.Entry<Integer, Boolean> entry : mUserUnlockedStatus.entrySet()) {
if (entry.getValue()) {
users.add(entry.getKey());
}
}
}
if (DBG) {
Slog.d(TAG, "notifyAllUnlockedUsers:" + users);
}
for (Integer i : users) {
sendSetUserLockStatusBinderCall(i, true);
}
}
private void sendSetCarServiceHelperBinderCall() {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
data.writeStrongBinder(mHelper.asBinder());
// void setCarServiceHelper(in IBinder helper)
sendBinderCallToCarService(data, ICAR_CALL_SET_CAR_SERVICE_HELPER);
}
private void sendSetUserLockStatusBinderCall(int userHandle, boolean unlocked) {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
data.writeInt(userHandle);
data.writeInt(unlocked ? 1 : 0);
// void setUserLockStatus(in int userHandle, in int unlocked)
sendBinderCallToCarService(data, ICAR_CALL_SET_USER_UNLOCK_STATUS);
}
private void sendSwitchUserBindercall(int userHandle) {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
data.writeInt(userHandle);
// void onSwitchUser(in int userHandle)
sendBinderCallToCarService(data, ICAR_CALL_SET_SWITCH_USER);
}
private void sendBinderCallToCarService(Parcel data, int callNumber) {
// Cannot depend on ICar which is defined in CarService, so handle binder call directly
// instead.
IBinder carService;
synchronized (mLock) {
carService = mCarService;
}
try {
carService.transact(IBinder.FIRST_CALL_TRANSACTION + callNumber,
data, null, Binder.FLAG_ONEWAY);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException from car service", e);
handleCarServiceCrash();
} finally {
data.recycle();
}
}
// 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());
}
private void handleCarServiceCrash() {
// Recovery behavior. Kill the system server and reset
// everything if enabled by the property.
boolean restartOnServiceCrash = SystemProperties.getBoolean(PROP_RESTART_RUNTIME, false);
dumpServiceStacks();
if (restartOnServiceCrash) {
Slog.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: " + "CarService crash");
Slog.w(TAG, "*** GOODBYE!");
Process.killProcess(Process.myPid());
System.exit(10);
} else {
Slog.w(TAG, "*** CARHELPER ignoring: " + "CarService crash");
}
}
private static native int nativeForceSuspend(int timeoutMs);
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;
}
}
}