blob: f8842d45b164d6b0be6e4b3a58c13b6049d233c3 [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.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.util.Slog;
import android.util.TimingsTraceLog;
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 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;
}
@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) {
boolean shouldNotify = false;
synchronized (mLock) {
mSystemBootCompleted = true;
if (mCarService != null) {
shouldNotify = true;
}
}
if (shouldNotify) {
notifyAllUnlockedUsers();
}
}
}
@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 {
Slog.i(TAG, "Switch to default user");
targetUserId = mCarUserManagerHelper.getInitialUser();
}
// If system user is the only user to unlock, handle it when system completes the boot.
if (targetUserId == UserHandle.USER_SYSTEM) {
return;
}
IActivityManager am = ActivityManager.getService();
if (am == null) {
Slog.wtf(TAG, "cannot get ActivityManagerService");
return;
}
TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTiming",
Trace.TRACE_TAG_SYSTEM_SERVER);
traceLog.traceBegin("User0Unlock");
try {
// This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not
// update the state and user 0 unlock happens twice.
if (!am.startUserInBackground(UserHandle.USER_SYSTEM)) {
// cannot start user
Slog.w(TAG, "cannot start system user");
} else if (!am.unlockUser(UserHandle.USER_SYSTEM, null, null, null)) {
// unlocking system user failed. But still continue for other setup.
Slog.w(TAG, "cannot unlock system user");
} else {
// user 0 started and unlocked
handleUserLockStatusChange(UserHandle.USER_SYSTEM, true);
}
} catch (RemoteException e) {
// should not happen for local call.
Slog.wtf("RemoteException from AMS", e);
}
traceLog.traceEnd();
// Do not unlock here to allow other stuffs done. Unlock will happen
// when system completes the boot.
// TODO(b/124460424) Unlock earlier?
traceLog.traceBegin("ForegroundUserStart");
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);
}
traceLog.traceEnd();
}
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;
}
}
}