1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.experimentalcar;
18 
19 import android.annotation.MainThread;
20 import android.annotation.Nullable;
21 import android.car.IExperimentalCar;
22 import android.car.IExperimentalCarHelper;
23 import android.car.experimental.CarDriverDistractionManager;
24 import android.car.experimental.CarTestDemoExperimentalFeatureManager;
25 import android.car.experimental.ExperimentalCar;
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.os.Binder;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.util.Log;
35 
36 import com.android.car.CarServiceBase;
37 import com.android.internal.annotations.GuardedBy;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.List;
44 
45 /**
46  * Implements IExperimentalCar for experimental features.
47  */
48 public final class IExperimentalCarImpl extends IExperimentalCar.Stub {
49 
50     private static final String TAG = "CAR.EXPIMPL";
51 
52     private static final List<String> ALL_AVAILABLE_FEATURES = Arrays.asList(
53             ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE,
54             ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE
55     );
56 
57     private final Context mContext;
58 
59     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
60 
61     private final Object mLock = new Object();
62 
63     @GuardedBy("mLock")
64     private boolean mReleased;
65 
66     @GuardedBy("mLock")
67     private IExperimentalCarHelper mHelper;
68 
69     @GuardedBy("mLock")
70     private ArrayList<CarServiceBase> mRunningServices = new ArrayList<>();
71 
IExperimentalCarImpl(Context context)72     public IExperimentalCarImpl(Context context) {
73         mContext = context;
74     }
75 
76     @Override
init(IExperimentalCarHelper helper, List<String> enabledFeatures)77     public void init(IExperimentalCarHelper helper, List<String> enabledFeatures) {
78         // From car service or unit testing only
79         assertCallingFromSystemProcessOrSelf();
80 
81         // dispatch to main thread as release is always done in main.
82         mMainThreadHandler.post(() -> {
83             synchronized (mLock) {
84                 if (mReleased) {
85                     Log.w(TAG, "init binder call after onDestroy, will ignore");
86                     return;
87                 }
88             }
89             ArrayList<CarServiceBase> services = new ArrayList<>();
90             ArrayList<String> startedFeatures = new ArrayList<>();
91             ArrayList<String> classNames = new ArrayList<>();
92             ArrayList<IBinder> binders = new ArrayList<>();
93 
94             // This cannot address inter-dependency. That requires re-ordering this in dependency
95             // order.
96             // That should be done when we find such needs. For now, each feature inside here should
97             // not have inter-dependency as they are all optional.
98             for (String feature : enabledFeatures) {
99                 CarServiceBase service = constructServiceForFeature(feature);
100                 if (service == null) {
101                     Log.e(TAG, "Failed to construct requested feature:" + feature);
102                     continue;
103                 }
104                 service.init();
105                 services.add(service);
106                 startedFeatures.add(feature);
107                 // If it is not IBinder, then it is internal feature.
108                 if (service instanceof IBinder) {
109                     binders.add((IBinder) service);
110                 } else {
111                     binders.add(null);
112                 }
113                 classNames.add(getClassNameForFeature(feature));
114             }
115             try {
116                 helper.onInitComplete(ALL_AVAILABLE_FEATURES, startedFeatures, classNames, binders);
117             } catch (RemoteException e) {
118                 Log.w(TAG, "Car service crashed?", e);
119                 // will be destroyed soon. Just continue and register services for possible cleanup.
120             }
121             synchronized (mLock) {
122                 mHelper = helper;
123                 mRunningServices.addAll(services);
124             }
125         });
126     }
127 
128     // should be called in Service.onDestroy
129     @MainThread
release()130     void release() {
131         // Copy to handle call release without lock
132         ArrayList<CarServiceBase> services;
133         synchronized (mLock) {
134             if (mReleased) {
135                 return;
136             }
137             mReleased = true;
138             services = new ArrayList<>(mRunningServices);
139             mRunningServices.clear();
140         }
141         for (CarServiceBase service : services) {
142             service.release();
143         }
144     }
145 
146     /** dump */
dump(FileDescriptor fd, PrintWriter writer, String[] args)147     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
148         ArrayList<CarServiceBase> services;
149         synchronized (mLock) {
150             writer.println("mReleased:" + mReleased);
151             writer.println("ALL_AVAILABLE_FEATURES:" + ALL_AVAILABLE_FEATURES);
152             services = new ArrayList<>(mRunningServices);
153         }
154         writer.println(" Number of running services:" + services.size());
155         int i = 0;
156         for (CarServiceBase service : services) {
157             writer.print(i + ":");
158             service.dump(writer);
159             i++;
160         }
161     }
162 
163     @Nullable
getClassNameForFeature(String featureName)164     private String getClassNameForFeature(String featureName) {
165         switch (featureName) {
166             case ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE:
167                 return CarTestDemoExperimentalFeatureManager.class.getName();
168             case ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE:
169                 return CarDriverDistractionManager.class.getName();
170             default:
171                 return null;
172         }
173     }
174 
175     @Nullable
constructServiceForFeature(String featureName)176     private CarServiceBase constructServiceForFeature(String featureName) {
177         switch (featureName) {
178             case ExperimentalCar.TEST_EXPERIMENTAL_FEATURE_SERVICE:
179                 return new TestDemoExperimentalFeatureService();
180             case ExperimentalCar.DRIVER_DISTRACTION_EXPERIMENTAL_FEATURE_SERVICE:
181                 return new DriverDistractionExperimentalFeatureService(mContext);
182             default:
183                 return null;
184         }
185     }
186 
assertCallingFromSystemProcessOrSelf()187     private static void assertCallingFromSystemProcessOrSelf() {
188         int uid = Binder.getCallingUid();
189         int pid = Binder.getCallingPid();
190         if (uid != Process.SYSTEM_UID && pid != Process.myPid()) {
191             throw new SecurityException("Only allowed from system or self, uid:" + uid
192                     + " pid:" + pid);
193         }
194     }
195 
196     /**
197      * Assert that a permission has been granted for the current context.
198      */
assertPermission(Context context, String permission)199     public static void assertPermission(Context context, String permission) {
200         if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
201             throw new SecurityException("requires " + permission);
202         }
203     }
204 }
205