| /* |
| * 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.internal.util.function.pooled.PooledLambda.obtainMessage; |
| |
| import static android.car.userlib.UserHelper.safeName; |
| |
| import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING; |
| import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED; |
| import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; |
| import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING; |
| import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED; |
| import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.automotive.watchdog.ICarWatchdogClient; |
| import android.automotive.watchdog.ICarWatchdogMonitor; |
| import android.automotive.watchdog.PowerCycle; |
| import android.automotive.watchdog.StateType; |
| import android.app.admin.DevicePolicyManager; |
| import android.car.userlib.CarUserManagerHelper; |
| import android.car.userlib.InitialUserSetter; |
| import android.car.userlib.UserHalHelper; |
| 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.graphics.Bitmap; |
| import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType; |
| import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction; |
| import android.hidl.manager.V1_0.IServiceManager; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Parcel; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.SystemProperties; |
| import android.os.Trace; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.sysprop.CarProperties; |
| import android.util.EventLog; |
| import android.util.Slog; |
| import android.util.SparseBooleanArray; |
| import android.util.TimeUtils; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.car.ExternalConstants.CarUserServiceConstants; |
| import com.android.internal.car.ExternalConstants.ICarConstants; |
| import com.android.internal.car.ExternalConstants.UserHalServiceConstants; |
| import com.android.internal.os.IResultReceiver; |
| import com.android.server.SystemService; |
| import com.android.server.SystemService.TargetUser; |
| import com.android.server.Watchdog; |
| import com.android.server.am.ActivityManagerService; |
| 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.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| 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 { |
| // Place holder for user name of the first user created. |
| 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 HAL timeout handling. |
| private static final int WHAT_HAL_TIMEOUT = 1; |
| // Message ID representing post-processing of process dumping. |
| private static final int WHAT_POST_PROCESS_DUMPING = 2; |
| // Message ID representing process killing. |
| private static final int WHAT_PROCESS_KILL = 3; |
| |
| // Typically there are ~2-5 ops while system and non-system users are starting. |
| private final int NUMBER_PENDING_OPERATIONS = 5; |
| |
| @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 InitialUserSetter mInitialUserSetter; |
| private final UserManager mUserManager; |
| private final CarLaunchParamsModifier mCarLaunchParamsModifier; |
| |
| private final boolean mHalEnabled; |
| private final int mHalTimeoutMs; |
| |
| // Handler is currently only used for handleHalTimedout(), which is removed once received. |
| private final Handler mHandler = new Handler(Looper.getMainLooper()); |
| |
| private final ProcessTerminator mProcessTerminator = new ProcessTerminator(); |
| |
| @GuardedBy("mLock") |
| private boolean mInitialized; |
| |
| /** |
| * End-to-end time (from process start) for unlocking the first non-system user. |
| */ |
| private long mFirstUnlockedUserDuration; |
| |
| /** |
| * Used to calculate how long it took to get the {@code INITIAL_USER_INFO} response from HAL: |
| * |
| * <ul> |
| * <li>{@code 0}: HAL not called yet |
| * <li>{@code <0}: stores the time HAL was called (multiplied by -1) |
| * <li>{@code >0}: contains the duration (in ms) |
| * </ul> |
| */ |
| private int mHalResponseTime; |
| |
| // TODO(b/150413515): rather than store Runnables, it would be more efficient to store some |
| // parcelables representing the operation, then pass them to setCarServiceHelper |
| @GuardedBy("mLock") |
| private ArrayList<Runnable> mPendingOperations; |
| |
| 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) { |
| Slog.d(TAG, "onServiceConnected:" + 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_SUSPEND; |
| try { |
| mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE, |
| powerCycle, /* arg2= */ 0); |
| if (DBG) { |
| Slog.d(TAG, "Notified car watchdog daemon a power cycle(" + powerCycle + ")"); |
| } |
| } catch (RemoteException | RuntimeException e) { |
| Slog.w(TAG, "Notifying system state change failed: " + e); |
| } |
| } |
| }; |
| |
| public CarServiceHelperService(Context context) { |
| this(context, |
| new CarUserManagerHelper(context), |
| /* initialUserSetter= */ null, |
| UserManager.get(context), |
| new CarLaunchParamsModifier(context), |
| new CarWatchdogDaemonHelper(TAG), |
| CarProperties.user_hal_enabled().orElse(false), |
| CarProperties.user_hal_timeout().orElse(5_000) |
| ); |
| } |
| |
| @VisibleForTesting |
| CarServiceHelperService( |
| Context context, |
| CarUserManagerHelper userManagerHelper, |
| InitialUserSetter initialUserSetter, |
| UserManager userManager, |
| CarLaunchParamsModifier carLaunchParamsModifier, |
| CarWatchdogDaemonHelper carWatchdogDaemonHelper, |
| boolean halEnabled, |
| int halTimeoutMs) { |
| super(context); |
| mContext = context; |
| mCarUserManagerHelper = userManagerHelper; |
| mUserManager = userManager; |
| mCarLaunchParamsModifier = carLaunchParamsModifier; |
| mCarWatchdogDaemonHelper = carWatchdogDaemonHelper; |
| boolean halValidUserHalSettings = false; |
| if (halEnabled) { |
| if (halTimeoutMs > 0) { |
| Slog.i(TAG, "User HAL enabled with timeout of " + halTimeoutMs + "ms"); |
| halValidUserHalSettings = true; |
| } else { |
| Slog.w(TAG, "Not using User HAL due to invalid value on userHalTimeoutMs config: " |
| + halTimeoutMs); |
| } |
| } |
| if (halValidUserHalSettings) { |
| mHalEnabled = true; |
| mHalTimeoutMs = halTimeoutMs; |
| } else { |
| mHalEnabled = false; |
| mHalTimeoutMs = -1; |
| Slog.i(TAG, "Not using User HAL"); |
| } |
| if (initialUserSetter == null) { |
| // Called from main constructor, which cannot pass a lambda referencing itself |
| mInitialUserSetter = new InitialUserSetter(context, |
| (u) -> setInitialUser(u), |
| !CarProperties.user_hal_enabled().orElse(false)); |
| } else { |
| mInitialUserSetter = initialUserSetter; |
| } |
| } |
| |
| @Override |
| public void onBootPhase(int phase) { |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_BOOT_PHASE, phase); |
| if (DBG) Slog.d(TAG, "onBootPhase:" + phase); |
| |
| TimingsTraceAndSlog t = newTimingsTraceAndSlog(); |
| if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { |
| t.traceBegin("onBootPhase.3pApps"); |
| mCarLaunchParamsModifier.init(); |
| checkForCarServiceConnection(t); |
| setupAndStartUsers(t); |
| checkForCarServiceConnection(t); |
| t.traceEnd(); |
| } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { |
| t.traceBegin("onBootPhase.completed"); |
| managePreCreatedUsers(); |
| boolean shouldNotify = false; |
| synchronized (mLock) { |
| mSystemBootCompleted = true; |
| if (mCarService != null) { |
| shouldNotify = true; |
| } |
| } |
| if (shouldNotify) { |
| notifyAllUnlockedUsers(); |
| } |
| try { |
| mCarWatchdogDaemonHelper.notifySystemStateChange( |
| StateType.BOOT_PHASE, phase, /* arg2= */ 0); |
| } catch (RemoteException | RuntimeException e) { |
| Slog.w(TAG, "Failed to notify boot phase change: " + e); |
| } |
| t.traceEnd(); |
| } |
| } |
| |
| @Override |
| public void onStart() { |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_START, mHalEnabled ? 1 : 0); |
| |
| IntentFilter filter = new IntentFilter(Intent.ACTION_REBOOT); |
| filter.addAction(Intent.ACTION_SHUTDOWN); |
| getContext().registerReceiverForAllUsers(mShutdownEventReceiver, filter, null, null); |
| mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener); |
| mCarWatchdogDaemonHelper.connect(); |
| Intent intent = new Intent(); |
| intent.setPackage("com.android.car"); |
| intent.setAction(ICarConstants.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 onUserUnlocking(@NonNull TargetUser user) { |
| if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)) return; |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKING, user.getUserIdentifier()); |
| if (DBG) Slog.d(TAG, "onUserUnlocking(" + user + ")"); |
| |
| sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, user); |
| } |
| |
| @Override |
| public void onUserUnlocked(@NonNull TargetUser user) { |
| if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)) return; |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKED, user.getUserIdentifier()); |
| if (DBG) Slog.d(TAG, "onUserUnlocked(" + user + ")"); |
| |
| if (mFirstUnlockedUserDuration == 0 && user.getUserIdentifier() != UserHandle.USER_SYSTEM) { |
| mFirstUnlockedUserDuration = SystemClock.elapsedRealtime() |
| - Process.getStartElapsedRealtime(); |
| Slog.i(TAG, "Time to unlock 1st user(" + user + "): " |
| + TimeUtils.formatDuration(mFirstUnlockedUserDuration)); |
| sendFirstUserUnlocked(user); |
| return; |
| } |
| 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) Slog.d(TAG, "onUserStarting(" + 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) Slog.d(TAG, "onUserStopping(" + 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) Slog.d(TAG, "onUserStopped(" + 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) Slog.d(TAG, "onUserSwitching(" + from + ">>" + to + ")"); |
| |
| sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, from, to); |
| int userId = to.getUserIdentifier(); |
| mCarLaunchParamsModifier.handleCurrentUserSwitching(userId); |
| synchronized (mLock) { |
| mLastSwitchedUser = userId; |
| if (mCarService == null) { |
| return; // The event will be delivered upon CarService connection. |
| } |
| } |
| } |
| |
| private boolean isPreCreated(@NonNull TargetUser user, int eventType) { |
| UserInfo userInfo = user.getUserInfo(); |
| if (userInfo == null) { |
| Slog.wtf(TAG, "no UserInfo on " + user + " on eventType " + eventType); |
| return false; |
| } |
| if (!userInfo.preCreated) return false; |
| |
| if (DBG) { |
| Slog.d(TAG, "Ignoring event of type " + eventType + " for pre-created user " |
| + userInfo.toFullString()); |
| } |
| return true; |
| } |
| |
| /** |
| * Queues a binder operation so it's called when the service is connected. |
| */ |
| private void queueOperationLocked(@NonNull Runnable operation) { |
| if (mPendingOperations == null) { |
| mPendingOperations = new ArrayList<>(NUMBER_PENDING_OPERATIONS); |
| } |
| mPendingOperations.add(operation); |
| } |
| |
| // 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(@NonNull TimingsTraceAndSlog t) { |
| synchronized (mLock) { |
| if (mCarService != null) { |
| return; |
| } |
| } |
| t.traceBegin("checkForCarServiceConnection"); |
| IBinder iBinder = ServiceManager.checkService("car_service"); |
| if (iBinder != null) { |
| if (DBG) { |
| Slog.d(TAG, "Car service found through ServiceManager:" + iBinder); |
| } |
| handleCarServiceConnection(iBinder); |
| } |
| t.traceEnd(); |
| } |
| |
| @VisibleForTesting |
| int getHalResponseTime() { |
| return mHalResponseTime; |
| } |
| |
| @VisibleForTesting |
| void setInitialHalResponseTime() { |
| mHalResponseTime = -((int) SystemClock.uptimeMillis()); |
| } |
| |
| @VisibleForTesting |
| void setFinalHalResponseTime() { |
| mHalResponseTime += (int) SystemClock.uptimeMillis(); |
| } |
| |
| @VisibleForTesting void handleCarServiceConnection(IBinder iBinder) { |
| int lastSwitchedUser; |
| boolean systemBootCompleted; |
| ArrayList<Runnable> pendingOperations; |
| 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; |
| pendingOperations = mPendingOperations; |
| mPendingOperations = null; |
| } |
| int numberOperations = pendingOperations == null ? 0 : pendingOperations.size(); |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_SVC_CONNECTED, numberOperations); |
| |
| Slog.i(TAG, "**CarService connected**"); |
| |
| sendSetCarServiceHelperBinderCall(); |
| if (pendingOperations != null) { |
| if (DBG) Slog.d(TAG, "Running " + numberOperations + " pending operations"); |
| TimingsTraceAndSlog t = newTimingsTraceAndSlog(); |
| t.traceBegin("send-pending-ops-" + numberOperations); |
| for (int i = 0; i < numberOperations; i++) { |
| Runnable operation = pendingOperations.get(i); |
| try { |
| operation.run(); |
| } catch (RuntimeException e) { |
| Slog.w(TAG, "exception running operation #" + i + ": " + e); |
| } |
| } |
| t.traceEnd(); |
| } |
| |
| if (systemBootCompleted) { |
| notifyAllUnlockedUsers(); |
| } |
| if (lastSwitchedUser != UserHandle.USER_NULL) { |
| sendSwitchUserBindercall(lastSwitchedUser); |
| } |
| } |
| |
| private TimingsTraceAndSlog newTimingsTraceAndSlog() { |
| return new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER); |
| } |
| |
| private void setupAndStartUsers(@NonNull TimingsTraceAndSlog t) { |
| 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; |
| } |
| t.traceBegin("setupAndStartUsers"); |
| if (mHalEnabled) { |
| Slog.i(TAG, "Delegating initial switching to HAL"); |
| setupAndStartUsersUsingHal(); |
| } else { |
| setupAndStartUsersDirecly(t); |
| } |
| t.traceEnd(); |
| } |
| |
| private void handleHalTimedout() { |
| synchronized (mLock) { |
| if (mInitialized) return; |
| } |
| |
| Slog.w(TAG, "HAL didn't respond in " + mHalTimeoutMs + "ms; using default behavior"); |
| setupAndStartUsersDirectly(); |
| } |
| |
| private void setupAndStartUsersUsingHal() { |
| mHandler.sendMessageDelayed(obtainMessage(CarServiceHelperService::handleHalTimedout, this) |
| .setWhat(WHAT_HAL_TIMEOUT), mHalTimeoutMs); |
| |
| // TODO(b/150413515): get rid of receiver once returned? |
| IResultReceiver receiver = new IResultReceiver.Stub() { |
| @Override |
| public void send(int resultCode, Bundle resultData) { |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_RESPONSE, resultCode); |
| |
| setFinalHalResponseTime(); |
| if (DBG) { |
| Slog.d(TAG, "Got result from HAL (" + |
| UserHalServiceConstants.statusToString(resultCode) + ") in " |
| + TimeUtils.formatDuration(mHalResponseTime)); |
| } |
| |
| mHandler.removeMessages(WHAT_HAL_TIMEOUT); |
| // TODO(b/150222501): log how long it took to receive the response |
| // TODO(b/150413515): print resultData as well on 2 logging calls below |
| synchronized (mLock) { |
| if (mInitialized) { |
| Slog.w(TAG, "Result from HAL came too late, ignoring: " |
| + UserHalServiceConstants.statusToString(resultCode)); |
| return; |
| } |
| } |
| |
| if (resultCode != UserHalServiceConstants.STATUS_OK) { |
| Slog.w(TAG, "Service returned non-ok status (" |
| + UserHalServiceConstants.statusToString(resultCode) |
| + "); using default behavior"); |
| fallbackToDefaultInitialUserBehavior(); |
| return; |
| } |
| |
| if (resultData == null) { |
| Slog.w(TAG, "Service returned null bundle"); |
| fallbackToDefaultInitialUserBehavior(); |
| return; |
| } |
| |
| int action = resultData.getInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION, |
| InitialUserInfoResponseAction.DEFAULT); |
| |
| switch (action) { |
| case InitialUserInfoResponseAction.DEFAULT: |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_DEFAULT_BEHAVIOR, |
| /* fallback= */ 0); |
| if (DBG) Slog.d(TAG, "User HAL returned DEFAULT behavior"); |
| setupAndStartUsersDirectly(); |
| return; |
| case InitialUserInfoResponseAction.SWITCH: |
| int userId = resultData.getInt(CarUserServiceConstants.BUNDLE_USER_ID); |
| startUserByHalRequest(userId); |
| return; |
| case InitialUserInfoResponseAction.CREATE: |
| String name = resultData |
| .getString(CarUserServiceConstants.BUNDLE_USER_NAME); |
| int flags = resultData.getInt(CarUserServiceConstants.BUNDLE_USER_FLAGS); |
| createUserByHalRequest(name, flags); |
| return; |
| default: |
| Slog.w(TAG, "Invalid InitialUserInfoResponseAction action: " + action); |
| } |
| fallbackToDefaultInitialUserBehavior(); |
| } |
| }; |
| int initialUserInfoRequestType = getInitialUserInfoRequestType(); |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_REQUEST, initialUserInfoRequestType); |
| |
| setInitialHalResponseTime(); |
| sendOrQueueGetInitialUserInfo(initialUserInfoRequestType, receiver); |
| } |
| |
| @VisibleForTesting |
| int getInitialUserInfoRequestType() { |
| if (!mCarUserManagerHelper.hasInitialUser()) { |
| return InitialUserInfoRequestType.FIRST_BOOT; |
| } |
| if (mContext.getPackageManager().isDeviceUpgrading()) { |
| return InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA; |
| } |
| return InitialUserInfoRequestType.COLD_BOOT; |
| } |
| |
| private void startUserByHalRequest(@UserIdInt int userId) { |
| if (userId <= 0) { |
| Slog.w(TAG, "invalid (or missing) user id sent by HAL: " + userId); |
| fallbackToDefaultInitialUserBehavior(); |
| return; |
| } |
| |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_START_USER, userId); |
| if (DBG) Slog.d(TAG, "Starting user " + userId + " as requested by HAL"); |
| |
| // It doesn't need to replace guest, as the switch would fail anyways if the requested user |
| // was a guest because it wouldn't exist. |
| mInitialUserSetter.switchUser(userId, /* replaceGuest= */ false); |
| } |
| |
| private void createUserByHalRequest(@Nullable String name, int halFlags) { |
| String friendlyName = "user with name '" + safeName(name) + "' and flags " |
| + UserHalHelper.userFlagsToString(halFlags); |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_CREATE_USER, halFlags, safeName(name)); |
| if (DBG) Slog.d(TAG, "HAL request creation of " + friendlyName); |
| |
| mInitialUserSetter.createUser(name, halFlags); |
| } |
| |
| private void fallbackToDefaultInitialUserBehavior() { |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_DEFAULT_BEHAVIOR, /* fallback= */ 1); |
| if (DBG) Slog.d(TAG, "Falling back to DEFAULT initial user behavior"); |
| setupAndStartUsersDirectly(); |
| } |
| |
| private void setupAndStartUsersDirectly() { |
| setupAndStartUsersDirecly(newTimingsTraceAndSlog()); |
| } |
| |
| private void setupAndStartUsersDirecly(@NonNull TimingsTraceAndSlog t) { |
| synchronized (mLock) { |
| if (mInitialized) { |
| Slog.wtf(TAG, "Already initialized", new Exception()); |
| return; |
| } |
| mInitialized = true; |
| } |
| |
| mInitialUserSetter.executeDefaultBehavior(/* replaceGuest= */ false); |
| } |
| |
| @VisibleForTesting |
| 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); |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_PRE_CREATION_REQUESTED, numberRequestedUsers, |
| numberRequestedGuests); |
| if (DBG) Slog.d(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; |
| } |
| |
| // 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 invalidPreCreatedUsers = new SparseBooleanArray(); |
| |
| // List of all pre-created users - it will be used to remove unused ones (when needed) |
| SparseBooleanArray existingPrecreatedUsers = 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()); |
| invalidPreCreatedUsers.append(user.id, /* notUsed=*/ true); |
| continue; |
| } |
| boolean isGuest = user.isGuest(); |
| existingPrecreatedUsers.put(user.id, isGuest); |
| if (isGuest) { |
| numberExistingGuests++; |
| } else { |
| numberExistingUsers++; |
| } |
| } |
| if (DBG) { |
| Slog.d(TAG, "managePreCreatedUsers(): system already has " + numberExistingGuests |
| + " pre-created guests," + numberExistingUsers + " pre-created users, and these" |
| + " invalid users: " + invalidPreCreatedUsers ); |
| } |
| |
| int numberGuestsToAdd = numberRequestedGuests - numberExistingGuests; |
| int numberUsersToAdd = numberRequestedUsers - numberExistingUsers; |
| int numberGuestsToRemove = numberExistingGuests - numberRequestedGuests; |
| int numberUsersToRemove = numberExistingUsers - numberRequestedUsers; |
| int numberInvalidUsersToRemove = invalidPreCreatedUsers.size(); |
| |
| EventLog.writeEvent(EventLogTags.CAR_HELPER_PRE_CREATION_STATUS, |
| numberExistingUsers, numberUsersToAdd, numberUsersToRemove, |
| numberExistingGuests, numberGuestsToAdd, numberGuestsToRemove, |
| numberInvalidUsersToRemove); |
| |
| if (numberGuestsToAdd == 0 && numberUsersToAdd == 0 && numberInvalidUsersToRemove == 0) { |
| if (DBG) Slog.d(TAG, "managePreCreatedUsers(): everything in sync"); |
| 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. |
| // TODO(b/152792035): refactor into a separate method so it can be spied on test cases |
| new Thread( () -> { |
| TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Async", |
| Trace.TRACE_TAG_SYSTEM_SERVER); |
| |
| t.traceBegin("preCreateUsers"); |
| if (numberUsersToAdd > 0) { |
| preCreateUsers(t, numberUsersToAdd, /* isGuest= */ false); |
| } |
| if (numberGuestsToAdd > 0) { |
| preCreateUsers(t, numberGuestsToAdd, /* isGuest= */ true); |
| } |
| |
| int totalNumberToRemove = Math.max(numberUsersToRemove, 0) |
| + Math.max(numberGuestsToRemove, 0); |
| if (DBG) Slog.d(TAG, "Must delete " + totalNumberToRemove + " pre-created users"); |
| if (totalNumberToRemove > 0) { |
| int[] usersToRemove = new int[totalNumberToRemove]; |
| int j = 0; |
| int numberUsersToRemoveLeft = numberUsersToRemove; |
| int numberGuestsToRemoveLeft = numberGuestsToRemove; |
| // TODO(b/152792035): avoid this loop by checking pre-created users in loop L586 |
| // and add users to remove there rather than going through the users again here |
| for (int i = 0; i < existingPrecreatedUsers.size(); i++) { |
| int userId = existingPrecreatedUsers.keyAt(i); |
| boolean isGuest = existingPrecreatedUsers.valueAt(i); |
| if (!isGuest && numberUsersToRemoveLeft > 0) { |
| if (DBG) Slog.d(TAG, "Marking user" + userId + " for removal"); |
| numberUsersToRemoveLeft--; |
| usersToRemove[j++] = userId; |
| } |
| if (isGuest && numberGuestsToRemoveLeft > 0 ) { |
| if (DBG) Slog.d(TAG, "Marking guest " + userId + " for removal"); |
| numberGuestsToRemoveLeft--; |
| usersToRemove[j++] = userId; |
| } |
| } |
| for (int userId : usersToRemove) { |
| Slog.i(TAG, "removing pre-created user with id " + userId); |
| mUserManager.removeUser(userId); |
| } |
| } |
| |
| t.traceEnd(); |
| |
| if (numberInvalidUsersToRemove > 0) { |
| t.traceBegin("removeInvalidPreCreatedUsers"); |
| for (int i = 0; i < numberInvalidUsersToRemove; i++) { |
| int userId = invalidPreCreatedUsers.keyAt(i); |
| Slog.i(TAG, "removing invalid pre-created user " + userId); |
| mUserManager.removeUser(userId); |
| } |
| t.traceEnd(); |
| } |
| }, "CarServiceHelperManagePreCreatedUsers").start(); |
| } |
| |
| private void preCreateUsers(@NonNull TimingsTraceAndSlog t, int size, boolean isGuest) { |
| String msg = isGuest ? "preCreateGuests-" + size : "preCreateUsers-" + size; |
| if (DBG) Slog.d(TAG, "preCreateUsers: " + msg); |
| 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/152792035): add unit test to verify exception is caught |
| @Nullable |
| public UserInfo preCreateUsers(@NonNull TimingsTraceAndSlog t, boolean isGuest) { |
| String traceMsg = "pre-create" + (isGuest ? "-guest" : "-user"); |
| t.traceBegin(traceMsg); |
| // NOTE: we want to get rid of UserManagerHelper, so let's call UserManager directly |
| String userType = |
| isGuest ? UserManager.USER_TYPE_FULL_GUEST : UserManager.USER_TYPE_FULL_SECONDARY; |
| UserInfo user = null; |
| try { |
| user = mUserManager.preCreateUser(userType); |
| if (user == null) { |
| logPrecreationFailure(traceMsg, /* cause= */ null); |
| } |
| } catch (Exception e) { |
| logPrecreationFailure(traceMsg, e); |
| } finally { |
| t.traceEnd(); |
| } |
| return user; |
| } |
| |
| /** |
| * Logs proper message when user pre-creation fails (most likely because there are too many). |
| */ |
| private void logPrecreationFailure(@NonNull String operation, @Nullable Exception cause) { |
| int maxNumberUsers = UserManager.getMaxSupportedUsers(); |
| int currentNumberUsers = mUserManager.getUserCount(); |
| StringBuilder message = new StringBuilder(operation.length() + 100) |
| .append(operation).append(" failed. Number users: ").append(currentNumberUsers) |
| .append(" Max: ").append(maxNumberUsers); |
| if (cause == null) { |
| Slog.w(TAG, message.toString()); |
| } else { |
| Slog.w(TAG, message.toString(), cause); |
| } |
| } |
| |
| 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(ICarConstants.CAR_SERVICE_INTERFACE); |
| data.writeStrongBinder(mHelper.asBinder()); |
| // void setCarServiceHelper(in IBinder helper) |
| sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_SET_CAR_SERVICE_HELPER); |
| } |
| |
| private void sendSetUserLockStatusBinderCall(@UserIdInt int userId, boolean unlocked) { |
| Parcel data = Parcel.obtain(); |
| data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE); |
| data.writeInt(userId); |
| data.writeInt(unlocked ? 1 : 0); |
| // void setUserLockStatus(in int userId, in int unlocked) |
| sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_SET_USER_UNLOCK_STATUS); |
| } |
| |
| private void sendSwitchUserBindercall(@UserIdInt int userId) { |
| Parcel data = Parcel.obtain(); |
| data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE); |
| data.writeInt(userId); |
| // void onSwitchUser(in int userId) |
| sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_ON_SWITCH_USER); |
| } |
| |
| private void sendUserLifecycleEvent(int eventType, @NonNull TargetUser user) { |
| sendUserLifecycleEvent(eventType, /* from= */ null, user); |
| } |
| |
| private void sendUserLifecycleEvent(int eventType, @Nullable TargetUser from, |
| @NonNull TargetUser to) { |
| long now = System.currentTimeMillis(); |
| synchronized (mLock) { |
| if (mCarService == null) { |
| if (DBG) Slog.d(TAG, "Queuing lifecycle event " + eventType + " for user " + to); |
| queueOperationLocked(() -> sendUserLifecycleEvent(eventType, now, from, to)); |
| return; |
| } |
| } |
| TimingsTraceAndSlog t = newTimingsTraceAndSlog(); |
| t.traceBegin("send-lifecycle-" + eventType + "-" + to.getUserIdentifier()); |
| sendUserLifecycleEvent(eventType, now, from, to); |
| t.traceEnd(); |
| } |
| |
| private void sendUserLifecycleEvent(int eventType, long timestamp, @Nullable TargetUser from, |
| @NonNull TargetUser to) { |
| int fromId = from == null ? UserHandle.USER_NULL : from.getUserIdentifier(); |
| Parcel data = Parcel.obtain(); |
| data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE); |
| data.writeInt(eventType); |
| data.writeLong(timestamp); |
| data.writeInt(fromId); |
| data.writeInt(to.getUserIdentifier()); |
| // void onUserLifecycleEvent(int eventType, long timestamp, int from, int to) |
| sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE); |
| } |
| |
| private void sendOrQueueGetInitialUserInfo(int requestType, @NonNull IResultReceiver receiver) { |
| synchronized (mLock) { |
| if (mCarService == null) { |
| if (DBG) Slog.d(TAG, "Queuing GetInitialUserInfo call for type " + requestType); |
| queueOperationLocked(() -> sendGetInitialUserInfo(requestType, receiver)); |
| return; |
| } |
| } |
| sendGetInitialUserInfo(requestType, receiver); |
| } |
| |
| private void sendGetInitialUserInfo(int requestType, @NonNull IResultReceiver receiver) { |
| Parcel data = Parcel.obtain(); |
| data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE); |
| data.writeInt(requestType); |
| data.writeInt(mHalTimeoutMs); |
| data.writeStrongBinder(receiver.asBinder()); |
| // void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver) |
| sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO); |
| } |
| |
| @VisibleForTesting |
| void setInitialUser(@Nullable UserInfo user) { |
| synchronized (mLock) { |
| if (mCarService == null) { |
| if (DBG) Slog.d(TAG, "Queuing setInitialUser() call"); |
| queueOperationLocked(() -> sendSetInitialUser(user)); |
| return; |
| } |
| } |
| sendSetInitialUser(user); |
| } |
| |
| private void sendSetInitialUser(@Nullable UserInfo user) { |
| if (DBG) Slog.d(TAG, "sendSetInitialUser(): " + user); |
| Parcel data = Parcel.obtain(); |
| data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE); |
| data.writeInt(user != null ? user.id : UserHandle.USER_NULL); |
| // void setInitialUser(int userId) |
| sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_SET_INITIAL_USER); |
| } |
| |
| private void sendFirstUserUnlocked(@NonNull TargetUser user) { |
| long now = System.currentTimeMillis(); |
| Parcel data = Parcel.obtain(); |
| data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE); |
| data.writeInt(user.getUserIdentifier()); |
| data.writeLong(now); |
| data.writeLong(mFirstUnlockedUserDuration); |
| data.writeInt(mHalResponseTime); |
| // void onFirstUserUnlocked(int userId, long timestamp, long duration, int halResponseTime) |
| sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED); |
| } |
| |
| 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; |
| } |
| if (carService == null) { |
| Slog.w(TAG, "Not calling txn " + callNumber + " because service is not bound yet", |
| new Exception()); |
| return; |
| } |
| int code = IBinder.FIRST_CALL_TRANSACTION + callNumber; |
| try { |
| if (VERBOSE) Slog.v(TAG, "calling one-way binder transaction with code " + code); |
| carService.transact(code, data, null, Binder.FLAG_ONEWAY); |
| if (VERBOSE) Slog.v(TAG, "finished one-way binder transaction with code " + code); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "RemoteException from car service", e); |
| handleCarServiceCrash(); |
| } catch (RuntimeException e) { |
| Slog.wtf(TAG, "Exception calling binder transaction " + callNumber + " (real code: " |
| + code + ")", e); |
| throw e; |
| } 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(), null); |
| } |
| |
| 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 void handleClientsNotResponding(@NonNull int[] pids) { |
| mProcessTerminator.requestTerminateProcess(pids); |
| } |
| |
| private void registerMonitorToWatchdogDaemon() { |
| try { |
| mCarWatchdogDaemonHelper.registerMonitor(mCarWatchdogMonitor); |
| } catch (RemoteException | RuntimeException e) { |
| Slog.w(TAG, "Cannot register to car watchdog daemon: " + e); |
| } |
| } |
| |
| private void killProcessAndReportToMonitor(int pid) { |
| String processName = getProcessName(pid); |
| Process.killProcess(pid); |
| Slog.w(TAG, "carwatchdog killed " + processName + " (pid: " + pid + ")"); |
| try { |
| mCarWatchdogDaemonHelper.tellDumpFinished(mCarWatchdogMonitor, pid); |
| } catch (RemoteException | RuntimeException e) { |
| Slog.w(TAG, "Cannot report monitor result to car watchdog daemon: " + 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) { |
| Slog.w(TAG, "Cannot read " + filename); |
| return unknownProcessName; |
| } |
| } |
| |
| 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; |
| } |
| |
| @Override |
| public void setDisplayWhitelistForUser(@UserIdInt int userId, int[] displayIds) { |
| mCarLaunchParamsModifier.setDisplayWhitelistForUser(userId, displayIds); |
| } |
| |
| @Override |
| public void setPassengerDisplays(int[] displayIdsForPassenger) { |
| mCarLaunchParamsModifier.setPassengerDisplays(displayIdsForPassenger); |
| } |
| } |
| |
| 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); |
| } |
| |
| @Override |
| public int getInterfaceVersion() { |
| return this.VERSION; |
| } |
| |
| @Override |
| public String getInterfaceHash() { |
| return this.HASH; |
| } |
| } |
| |
| 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) { |
| Slog.d(TAG, "Dumping and killing process(pid: " + 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) { |
| Slog.w(TAG, "Cannot get process information: " + e); |
| return; |
| } |
| nativePids.addAll(getInterestingNativePids()); |
| long startDumpTime = SystemClock.uptimeMillis(); |
| ActivityManagerService.dumpStackTraces(javaPids, null, null, nativePids, null); |
| long dumpTime = SystemClock.uptimeMillis() - startDumpTime; |
| if (DBG) { |
| Slog.d(TAG, "Dumping process took " + dumpTime + "ms"); |
| } |
| // 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"; |
| } |
| } |
| } |