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