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