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