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.car;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import android.annotation.Nullable;
22 import android.car.IExperimentalCar;
23 import android.car.IExperimentalCarHelper;
24 import android.car.builtin.util.Slogf;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.ServiceConnection;
29 import android.os.IBinder;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.util.ArrayMap;
34 import android.util.proto.ProtoOutputStream;
35 
36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
37 import com.android.car.internal.util.IndentingPrintWriter;
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /**
45  * Controls binding to ExperimentalCarService and interfaces for experimental features.
46  */
47 public final class CarExperimentalFeatureServiceController implements CarServiceBase {
48 
49     private static final String TAG = CarLog.tagFor(CarExperimentalFeatureServiceController.class);
50 
51     private final Context mContext;
52 
53     private final ServiceConnection mServiceConnection = new ServiceConnection() {
54         @Override
55         public void onServiceConnected(ComponentName name, IBinder service) {
56             IExperimentalCar experimentalCar;
57             synchronized (mLock) {
58                 experimentalCar = IExperimentalCar.Stub.asInterface(service);
59                 mExperimentalCar = experimentalCar;
60             }
61             if (experimentalCar == null) {
62                 Slogf.e(TAG, "Experimental car returned null binder");
63                 return;
64             }
65             CarFeatureController featureController = CarLocalServices.getService(
66                     CarFeatureController.class);
67             List<String> enabledExperimentalFeatures =
68                     featureController.getEnabledExperimentalFeatures();
69             try {
70                 experimentalCar.init(mHelper, enabledExperimentalFeatures);
71             } catch (RemoteException e) {
72                 Slogf.e(TAG, "Experimental car service crashed", e);
73             }
74         }
75 
76         @Override
77         public void onServiceDisconnected(ComponentName name) {
78             resetFeatures();
79         }
80     };
81 
82     private final IExperimentalCarHelper mHelper = new IExperimentalCarHelper.Stub() {
83         @Override
84         public void onInitComplete(List<String> allAvailableFeatures, List<String> startedFeatures,
85                 List<String> classNames, List<IBinder> binders) {
86             if (allAvailableFeatures == null) {
87                 Slogf.e(TAG, "Experimental car passed null allAvailableFeatures");
88                 return;
89             }
90             if (startedFeatures == null || classNames == null || binders == null) {
91                 Slogf.i(TAG, "Nothing enabled in Experimental car");
92                 return;
93             }
94             int sizeOfStartedFeatures = startedFeatures.size();
95             if (sizeOfStartedFeatures != classNames.size()
96                     || sizeOfStartedFeatures != binders.size()) {
97                 Slogf.e(TAG, "Experimental car passed wrong lists of enabled features, "
98                         + "startedFeatures:" + startedFeatures + " classNames:" + classNames
99                         + " binders:" + binders);
100             }
101             // Do conversion to make indexed accesses
102             ArrayList<String> classNamesInArray = new ArrayList<>(classNames);
103             ArrayList<IBinder> bindersInArray = new ArrayList<>(binders);
104             synchronized (mLock) {
105                 for (int i = 0; i < startedFeatures.size(); i++) {
106                     mEnabledFeatures.put(startedFeatures.get(i),
107                             new FeatureInfo(classNamesInArray.get(i),
108                                     bindersInArray.get(i)));
109                 }
110             }
111             CarFeatureController featureController = CarLocalServices.getService(
112                     CarFeatureController.class);
113             featureController.setAvailableExperimentalFeatureList(allAvailableFeatures);
114             Slogf.i(TAG, "Available experimental features:" + allAvailableFeatures);
115             Slogf.i(TAG, "Started experimental features:" + startedFeatures);
116         }
117     };
118 
119     private final Object mLock = new Object();
120 
121     @GuardedBy("mLock")
122     private IExperimentalCar mExperimentalCar;
123 
124     @GuardedBy("mLock")
125     private final ArrayMap<String, FeatureInfo> mEnabledFeatures = new ArrayMap<>();
126 
127     @GuardedBy("mLock")
128     private boolean mBound;
129 
130     private static class FeatureInfo {
131         public final String className;
132         public final IBinder binder;
133 
FeatureInfo(String className, IBinder binder)134         FeatureInfo(String className, IBinder binder) {
135             this.className = className;
136             this.binder = binder;
137         }
138     }
139 
CarExperimentalFeatureServiceController(Context context)140     public CarExperimentalFeatureServiceController(Context context) {
141         mContext = context;
142     }
143 
144     @Override
init()145     public void init() {
146         // Do binding only for real car servie
147         Intent intent = new Intent();
148         intent.setComponent(new ComponentName("com.android.experimentalcar",
149                 "com.android.experimentalcar.ExperimentalCarService"));
150         boolean bound = bindService(intent);
151         if (!bound) {
152             Slogf.e(TAG, "Cannot bind to experimental car service, intent:" + intent);
153         }
154         synchronized (mLock) {
155             mBound = bound;
156         }
157     }
158 
159     /**
160      * Bind service. Separated for testing.
161      * Test will override this. Default behavior will not bind if it is not real run (=system uid).
162      */
163     @VisibleForTesting
bindService(Intent intent)164     public boolean bindService(Intent intent) {
165         int myUid = Process.myUid();
166         if (myUid != Process.SYSTEM_UID) {
167             Slogf.w(TAG, "Binding experimental service skipped as this may be test env, uid:"
168                     + myUid);
169             return false;
170         }
171         try {
172             return mContext.bindServiceAsUser(intent, mServiceConnection,
173                     Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
174         } catch (Exception e) {
175             // Do not crash car service for case like package not found and etc.
176             Slogf.e(TAG, "Cannot bind to experimental car service", e);
177             return false;
178         }
179     }
180 
181     @Override
release()182     public void release() {
183         synchronized (mLock) {
184             if (mBound) {
185                 mContext.unbindService(mServiceConnection);
186             }
187             mBound = false;
188             resetFeatures();
189         }
190     }
191 
192     @Override
193     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)194     public void dump(IndentingPrintWriter writer) {
195         writer.println("*CarExperimentalFeatureServiceController*");
196 
197         synchronized (mLock) {
198             writer.println(" mEnabledFeatures, number of features:" + mEnabledFeatures.size()
199                     + ", format: (feature, class)");
200             for (int i = 0; i < mEnabledFeatures.size(); i++) {
201                 String feature = mEnabledFeatures.keyAt(i);
202                 FeatureInfo info = mEnabledFeatures.valueAt(i);
203                 writer.println(feature + "," + info.className);
204             }
205             writer.println("mBound:" + mBound);
206         }
207     }
208 
209     @Override
210     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)211     public void dumpProto(ProtoOutputStream proto) {}
212 
213     /**
214      * Returns class name for experimental feature.
215      */
216     @Nullable
getCarManagerClassForFeature(String featureName)217     public String getCarManagerClassForFeature(String featureName) {
218         FeatureInfo info;
219         synchronized (mLock) {
220             info = mEnabledFeatures.get(featureName);
221         }
222         if (info == null) {
223             return null;
224         }
225         return info.className;
226     }
227 
228     /**
229      * Returns service binder for experimental feature.
230      */
231     @Nullable
getCarService(String serviceName)232     public IBinder getCarService(String serviceName) {
233         FeatureInfo info;
234         synchronized (mLock) {
235             info = mEnabledFeatures.get(serviceName);
236         }
237         if (info == null) {
238             return null;
239         }
240         return info.binder;
241     }
242 
resetFeatures()243     private void resetFeatures() {
244         synchronized (mLock) {
245             mExperimentalCar = null;
246             mEnabledFeatures.clear();
247         }
248     }
249 }
250