| /* |
| * Copyright (C) 2021 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.updatable; |
| |
| import static android.view.Display.INVALID_DISPLAY; |
| |
| 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.INVALID_GID; |
| import static com.android.car.internal.common.CommonConstants.INVALID_PID; |
| import static com.android.car.internal.common.CommonConstants.INVALID_USER_ID; |
| import static com.android.car.internal.util.VersionUtils.isPlatformVersionAtLeastU; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.car.ICar; |
| import android.car.ICarResultReceiver; |
| import android.car.builtin.os.UserManagerHelper; |
| import android.car.builtin.util.EventLogHelper; |
| import android.car.builtin.util.Slogf; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| |
| import com.android.car.internal.ICarServiceHelper; |
| import com.android.car.internal.ICarSystemServerClient; |
| import com.android.car.internal.util.IndentingPrintWriter; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.Keep; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.car.CarServiceHelperInterface; |
| import com.android.internal.car.CarServiceHelperServiceUpdatable; |
| import com.android.server.wm.CarActivityInterceptorUpdatableImpl; |
| import com.android.server.wm.CarLaunchParamsModifierInterface; |
| import com.android.server.wm.CarLaunchParamsModifierUpdatable; |
| import com.android.server.wm.CarLaunchParamsModifierUpdatableImpl; |
| |
| import java.io.File; |
| import java.io.PrintWriter; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| import java.util.function.BiConsumer; |
| |
| /** |
| * Implementation of the abstract class CarServiceHelperUpdatable |
| */ |
| @Keep |
| public final class CarServiceHelperServiceUpdatableImpl |
| implements CarServiceHelperServiceUpdatable, Executor { |
| |
| @VisibleForTesting |
| static final String TAG = "CarServiceHelper"; |
| |
| private static final boolean DBG = false; |
| |
| private static final String PROP_RESTART_RUNTIME = "ro.car.recovery.restart_runtime.enabled"; |
| |
| private static final long CAR_SERVICE_BINDER_CALL_TIMEOUT_MS = 15_000; |
| |
| private final Runnable mCallbackForCarServiceUnresponsiveness; |
| |
| // exit code for |
| private static final int STATUS_CODE_To_EXIT = 10; |
| |
| private static final String CAR_SERVICE_PACKAGE = "com.android.car"; |
| |
| private final Context mContext; |
| private final Object mLock = new Object(); |
| @GuardedBy("mLock") |
| private ICar mCarServiceBinder; |
| |
| private final Handler mHandler; |
| private final HandlerThread mHandlerThread = new HandlerThread( |
| CarServiceHelperServiceUpdatableImpl.class.getSimpleName()); |
| |
| @VisibleForTesting |
| final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl(); |
| |
| private final CarServiceConnectedCallback mCarServiceConnectedCallback = |
| new CarServiceConnectedCallback(); |
| |
| private final CarServiceProxy mCarServiceProxy; |
| |
| private final CarServiceHelperInterface mCarServiceHelperInterface; |
| |
| private final CarLaunchParamsModifierUpdatableImpl mCarLaunchParamsModifierUpdatable; |
| private final CarActivityInterceptorUpdatableImpl mCarActivityInterceptorUpdatable; |
| |
| public CarServiceHelperServiceUpdatableImpl(Context context, |
| CarServiceHelperInterface carServiceHelperInterface, |
| CarLaunchParamsModifierInterface carLaunchParamsModifierInterface) { |
| this(context, carServiceHelperInterface, carLaunchParamsModifierInterface, |
| /* carServiceProxy= */ null); |
| } |
| |
| @VisibleForTesting |
| CarServiceHelperServiceUpdatableImpl(Context context, |
| CarServiceHelperInterface carServiceHelperInterface, |
| CarLaunchParamsModifierInterface carLaunchParamsModifierInterface, |
| @Nullable CarServiceProxy carServiceProxy) { |
| mContext = context; |
| mHandlerThread.start(); |
| mHandler = new Handler(mHandlerThread.getLooper()); |
| mCarServiceHelperInterface = carServiceHelperInterface; |
| mCarLaunchParamsModifierUpdatable = new CarLaunchParamsModifierUpdatableImpl( |
| carLaunchParamsModifierInterface); |
| if (isPlatformVersionAtLeastU()) { |
| mCarActivityInterceptorUpdatable = new CarActivityInterceptorUpdatableImpl(); |
| } else { |
| mCarActivityInterceptorUpdatable = null; |
| } |
| // carServiceProxy is Nullable because it is not possible to construct carServiceProxy with |
| // "this" object in the previous constructor as CarServiceHelperServiceUpdatableImpl has |
| // not been fully constructed. |
| mCarServiceProxy = carServiceProxy == null ? new CarServiceProxy(this) : carServiceProxy; |
| mCallbackForCarServiceUnresponsiveness = () -> handleCarServiceUnresponsive(); |
| } |
| |
| 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(); |
| } |
| }; |
| |
| @Override |
| public void onStart() { |
| Intent intent = new Intent(CAR_SERVICE_INTERFACE).setPackage(CAR_SERVICE_PACKAGE); |
| Context userContext = mContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0); |
| if (!userContext.bindService(intent, Context.BIND_AUTO_CREATE, this, |
| mCarServiceConnection)) { |
| Slogf.wtf(TAG, "cannot start car service"); |
| } |
| } |
| |
| @Override // From Executor |
| public void execute(Runnable command) { |
| mHandler.post(command); |
| } |
| |
| @Override |
| public void onUserRemoved(UserHandle user) { |
| mCarServiceProxy.onUserRemoved(user); |
| } |
| |
| @Override |
| public void onFactoryReset(BiConsumer<Integer, Bundle> callback) { |
| ICarResultReceiver resultReceiver = new ICarResultReceiver.Stub() { |
| @Override |
| public void send(int resultCode, Bundle resultData) throws RemoteException { |
| callback.accept(resultCode, resultData); |
| |
| } |
| }; |
| mCarServiceProxy.onFactoryReset(resultReceiver); |
| } |
| |
| @Override |
| public void initBootUser() { |
| mCarServiceProxy.initBootUser(); |
| } |
| |
| @Override |
| public CarLaunchParamsModifierUpdatable getCarLaunchParamsModifierUpdatable() { |
| return mCarLaunchParamsModifierUpdatable; |
| } |
| |
| @Override |
| public CarActivityInterceptorUpdatableImpl getCarActivityInterceptorUpdatable() { |
| return mCarActivityInterceptorUpdatable; |
| } |
| |
| @VisibleForTesting |
| void handleCarServiceConnection(IBinder iBinder) { |
| synchronized (mLock) { |
| if (mCarServiceBinder == ICar.Stub.asInterface(iBinder)) { |
| return; // already connected. |
| } |
| if (DBG) { |
| Slogf.d(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, |
| iBinder); |
| } |
| mCarServiceBinder = ICar.Stub.asInterface(iBinder); |
| Slogf.i(TAG, "**CarService connected**"); |
| } |
| |
| EventLogHelper.writeCarHelperServiceConnected(); |
| |
| // Post mCallbackForCarServiceUnresponsiveness before setting system server connection |
| // because CarService may respond before the sendSetSystemServerConnectionsCall call |
| // returns and try to remove mCallbackForCarServiceUnresponsiveness from the handler. |
| // Thus, posting this callback after setting system server connection may result in a race |
| // condition where the callback is never removed from the handler. |
| mHandler.removeCallbacks(mCallbackForCarServiceUnresponsiveness); |
| mHandler.postDelayed(mCallbackForCarServiceUnresponsiveness, |
| CAR_SERVICE_BINDER_CALL_TIMEOUT_MS); |
| |
| sendSetSystemServerConnectionsCall(); |
| } |
| |
| @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.removeCallbacks(mCallbackForCarServiceUnresponsiveness); |
| |
| mCarServiceHelperInterface.dumpServiceStacks(); |
| if (restartOnServiceCrash) { |
| Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService crash"); |
| Slogf.w(TAG, "*** GOODBYE!"); |
| Process.killProcess(Process.myPid()); |
| System.exit(STATUS_CODE_To_EXIT); |
| } else { |
| Slogf.w(TAG, "*** CARHELPER ignoring: CarService crash"); |
| } |
| } |
| |
| private void sendSetSystemServerConnectionsCall() { |
| ICar binder; |
| synchronized (mLock) { |
| binder = mCarServiceBinder; |
| } |
| try { |
| binder.setSystemServerConnections(mHelper, mCarServiceConnectedCallback); |
| } catch (RemoteException e) { |
| Slogf.w(TAG, e, "RemoteException from car service"); |
| handleCarServiceCrash(); |
| } catch (RuntimeException e) { |
| Slogf.wtf(TAG, e, "Exception calling setSystemServerConnections"); |
| throw e; |
| } |
| } |
| |
| 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(STATUS_CODE_To_EXIT); |
| } |
| |
| @Override |
| public void sendUserLifecycleEvent(int eventType, UserHandle userFrom, UserHandle userTo) { |
| mCarServiceProxy.sendUserLifecycleEvent(eventType, |
| userFrom == null ? UserManagerHelper.USER_NULL : userFrom.getIdentifier(), |
| userTo.getIdentifier()); |
| } |
| |
| @Override |
| public void dump(PrintWriter writer, String[] args) { |
| if (args != null && args.length > 0 && "--user-metrics-only".equals(args[0])) { |
| mCarServiceProxy.dumpUserMetrics(new IndentingPrintWriter(writer)); |
| return; |
| } |
| |
| if (args != null && args.length > 0 && "--dump-service-stacks".equals(args[0])) { |
| File file = mCarServiceHelperInterface.dumpServiceStacks(); |
| if (file != null) { |
| writer.printf("dumpServiceStacks ANR file path=%s\n", file.getAbsolutePath()); |
| } else { |
| writer.printf("dumpServiceStacks no ANR file.\n"); |
| } |
| return; |
| } |
| |
| mCarServiceProxy.dump(new IndentingPrintWriter(writer)); |
| } |
| |
| @VisibleForTesting |
| final class ICarServiceHelperImpl extends ICarServiceHelper.Stub { |
| |
| @Override |
| public void setDisplayAllowlistForUser(int userId, int[] displayIds) { |
| mCarLaunchParamsModifierUpdatable.setDisplayAllowListForUser(userId, displayIds); |
| } |
| |
| @Override |
| public void setPassengerDisplays(int[] displayIdsForPassenger) { |
| mCarLaunchParamsModifierUpdatable.setPassengerDisplays(displayIdsForPassenger); |
| } |
| |
| @Override |
| public void setSourcePreferredComponents(boolean enableSourcePreferred, |
| @Nullable List<ComponentName> sourcePreferredComponents) { |
| mCarLaunchParamsModifierUpdatable.setSourcePreferredComponents( |
| enableSourcePreferred, sourcePreferredComponents); |
| } |
| |
| @Override |
| public int setPersistentActivity(ComponentName activity, int displayId, int featureId) { |
| return mCarLaunchParamsModifierUpdatable.setPersistentActivity( |
| activity, displayId, featureId); |
| } |
| |
| @Override |
| public void setPersistentActivitiesOnRootTask(@NonNull List<ComponentName> activities, |
| IBinder rootTaskToken) { |
| mCarActivityInterceptorUpdatable.setPersistentActivityOnRootTask(activities, |
| rootTaskToken); |
| } |
| |
| @Override |
| public void setSafetyMode(boolean safe) { |
| mCarServiceHelperInterface.setSafetyMode(safe); |
| } |
| |
| @Override |
| public UserHandle createUserEvenWhenDisallowed(String name, String userType, int flags) { |
| return mCarServiceHelperInterface.createUserEvenWhenDisallowed(name, userType, flags); |
| } |
| |
| @Override |
| public void sendInitialUser(UserHandle user) { |
| mCarServiceProxy.saveInitialUser(user); |
| } |
| |
| @Override |
| public void setProcessGroup(int pid, int group) { |
| if (!isPlatformVersionAtLeastU()) { |
| return; |
| } |
| mCarServiceHelperInterface.setProcessGroup(pid, group); |
| } |
| |
| @Override |
| public int getProcessGroup(int pid) { |
| if (isPlatformVersionAtLeastU()) { |
| return mCarServiceHelperInterface.getProcessGroup(pid); |
| } |
| return INVALID_GID; |
| } |
| |
| @Override |
| public int getMainDisplayAssignedToUser(int userId) { |
| if (isPlatformVersionAtLeastU()) { |
| return mCarServiceHelperInterface.getMainDisplayAssignedToUser(userId); |
| } |
| return INVALID_DISPLAY; |
| } |
| |
| @Override |
| public int getUserAssignedToDisplay(int displayId) { |
| if (isPlatformVersionAtLeastU()) { |
| return mCarServiceHelperInterface.getUserAssignedToDisplay(displayId); |
| } |
| return INVALID_USER_ID; |
| } |
| |
| @Override |
| public boolean startUserInBackgroundVisibleOnDisplay(int userId, int displayId) { |
| if (isPlatformVersionAtLeastU()) { |
| return mCarServiceHelperInterface.startUserInBackgroundVisibleOnDisplay( |
| userId, displayId); |
| } |
| return false; |
| } |
| |
| @Override |
| public void setProcessProfile(int pid, int uid, @NonNull String profile) { |
| if (!isPlatformVersionAtLeastU()) { |
| return; |
| } |
| mCarServiceHelperInterface.setProcessProfile(pid, uid, profile); |
| } |
| |
| @Override |
| public int fetchAidlVhalPid() { |
| if (isPlatformVersionAtLeastU()) { |
| return mCarServiceHelperInterface.fetchAidlVhalPid(); |
| } |
| return INVALID_PID; |
| } |
| } |
| |
| private final class CarServiceConnectedCallback extends ICarResultReceiver.Stub { |
| @Override |
| public void send(int resultCode, Bundle resultData) { |
| mHandler.removeCallbacks(mCallbackForCarServiceUnresponsiveness); |
| |
| IBinder binder; |
| if (resultData == null |
| || (binder = resultData.getBinder(ICAR_SYSTEM_SERVER_CLIENT)) == null) { |
| Slogf.wtf(TAG, "setSystemServerConnections return NULL data or Binder."); |
| handleCarServiceUnresponsive(); |
| return; |
| } |
| |
| ICarSystemServerClient carService = ICarSystemServerClient.Stub.asInterface(binder); |
| mCarServiceProxy.handleCarServiceConnection(carService); |
| } |
| } |
| } |