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