/* * Copyright (C) 2019 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.experimentalcar; import android.annotation.MainThread; import android.annotation.Nullable; import android.car.IExperimentalCar; import android.car.IExperimentalCarHelper; import android.car.experimental.CarDriverDistractionManager; import android.car.experimental.CarTestDemoExperimentalFeatureManager; import android.car.experimental.ExperimentalCar; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.util.Log; import com.android.car.CarServiceBase; import com.android.car.internal.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Implements IExperimentalCar for experimental features. */ public final class IExperimentalCarImpl extends IExperimentalCar.Stub { private static final String TAG = "CAR.EXPIMPL"; private static final List<String> ALL_AVAILABLE_FEATURES = Arrays.asList( ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE, ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE ); private final Context mContext; private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); private final Object mLock = new Object(); @GuardedBy("mLock") private boolean mReleased; @GuardedBy("mLock") private ArrayList<CarServiceBase> mRunningServices = new ArrayList<>(); public IExperimentalCarImpl(Context context) { mContext = context; } @Override public void init(IExperimentalCarHelper helper, List<String> enabledFeatures) { // From car service or unit testing only assertCallingFromSystemProcessOrSelf(); // dispatch to main thread as release is always done in main. mMainThreadHandler.post(() -> { synchronized (mLock) { if (mReleased) { Log.w(TAG, "init binder call after onDestroy, will ignore"); return; } } ArrayList<CarServiceBase> services = new ArrayList<>(); ArrayList<String> startedFeatures = new ArrayList<>(); ArrayList<String> classNames = new ArrayList<>(); ArrayList<IBinder> binders = new ArrayList<>(); // This cannot address inter-dependency. That requires re-ordering this in dependency // order. // That should be done when we find such needs. For now, each feature inside here should // not have inter-dependency as they are all optional. for (String feature : enabledFeatures) { CarServiceBase service = constructServiceForFeature(feature); if (service == null) { Log.e(TAG, "Failed to construct requested feature:" + feature); continue; } service.init(); services.add(service); startedFeatures.add(feature); // If it is not IBinder, then it is internal feature. if (service instanceof IBinder) { binders.add((IBinder) service); } else { binders.add(null); } classNames.add(getClassNameForFeature(feature)); } try { helper.onInitComplete(ALL_AVAILABLE_FEATURES, startedFeatures, classNames, binders); } catch (RemoteException e) { Log.w(TAG, "Car service crashed?", e); // will be destroyed soon. Just continue and register services for possible cleanup. } synchronized (mLock) { mRunningServices.addAll(services); } }); } // should be called in Service.onDestroy @MainThread void release() { // Copy to handle call release without lock ArrayList<CarServiceBase> services; synchronized (mLock) { if (mReleased) { return; } mReleased = true; services = new ArrayList<>(mRunningServices); mRunningServices.clear(); } for (CarServiceBase service : services) { service.release(); } } /** dump */ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { writer.println("Permission Denial: can't dump ExperimentalCarService from from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + " without permission " + android.Manifest.permission.DUMP); return; } try (IndentingPrintWriter pw = new IndentingPrintWriter(writer)) { ArrayList<CarServiceBase> services; synchronized (mLock) { pw.printf("mReleased: %b\n", mReleased); pw.printf("ALL_AVAILABLE_FEATURES: %s\n", ALL_AVAILABLE_FEATURES); services = new ArrayList<>(mRunningServices); } pw.printf(" Number of running services: %d\n", services.size()); int i = 0; for (CarServiceBase service : services) { writer.printf("%d:\n", i); pw.increaseIndent(); service.dump(pw); pw.decreaseIndent(); i++; } } } @Nullable private String getClassNameForFeature(String featureName) { switch (featureName) { case ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE: return CarTestDemoExperimentalFeatureManager.class.getName(); case ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE: return CarDriverDistractionManager.class.getName(); default: return null; } } @Nullable private CarServiceBase constructServiceForFeature(String featureName) { switch (featureName) { case ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE: return new TestDemoExperimentalFeatureService(); case ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE: return new DriverDistractionExperimentalFeatureService(mContext); default: return null; } } private static void assertCallingFromSystemProcessOrSelf() { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); if (uid != Process.SYSTEM_UID && pid != Process.myPid()) { throw new SecurityException("Only allowed from system or self, uid:" + uid + " pid:" + pid); } } /** * Assert that a permission has been granted for the current context. */ public static void assertPermission(Context context, String permission) { if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("requires " + permission); } } }