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