1 /* 2 * Copyright (C) 2022 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.server.sdksandbox; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.content.pm.ServiceInfo; 28 import android.os.RemoteException; 29 import android.util.ArrayMap; 30 import android.util.Log; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.modules.utils.build.SdkLevel; 34 import com.android.sdksandbox.ISdkSandboxService; 35 import com.android.server.LocalManagerRegistry; 36 import com.android.server.am.ActivityManagerLocal; 37 38 import java.io.PrintWriter; 39 import java.util.Objects; 40 41 import javax.annotation.concurrent.ThreadSafe; 42 43 /** 44 * Implementation of {@link SdkSandboxServiceProvider}. 45 * 46 * @hide 47 */ 48 @ThreadSafe 49 class SdkSandboxServiceProviderImpl implements SdkSandboxServiceProvider { 50 51 private static final String TAG = "SdkSandboxManager"; 52 53 private final Object mLock = new Object(); 54 55 private final Context mContext; 56 private final ActivityManagerLocal mActivityManagerLocal; 57 58 @GuardedBy("mLock") 59 private final ArrayMap<CallingInfo, SdkSandboxConnection> mAppSdkSandboxConnections = 60 new ArrayMap<>(); 61 SdkSandboxServiceProviderImpl(Context context)62 SdkSandboxServiceProviderImpl(Context context) { 63 mContext = context; 64 mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class); 65 } 66 67 @Override 68 @Nullable bindService(CallingInfo callingInfo, ServiceConnection serviceConnection)69 public void bindService(CallingInfo callingInfo, ServiceConnection serviceConnection) { 70 synchronized (mLock) { 71 SdkSandboxConnection sdkSandboxConnection = getSdkSandboxConnectionLocked(callingInfo); 72 if (sdkSandboxConnection != null && sdkSandboxConnection.getStatus() != NON_EXISTENT) { 73 // The sandbox is either already created or is in the process of being 74 // created/restarted. Do not bind again. Note that later restarts can take a while, 75 // since retries are done exponentially. 76 Log.i(TAG, "SDK sandbox for " + callingInfo + " is already created"); 77 return; 78 } 79 80 Log.i(TAG, "Binding sdk sandbox for " + callingInfo); 81 82 ComponentName componentName = getServiceComponentName(); 83 if (componentName == null) { 84 Log.e(TAG, "Failed to find sdk sandbox service"); 85 notifyFailedBinding(serviceConnection); 86 return; 87 } 88 final Intent intent = new Intent().setComponent(componentName); 89 90 sdkSandboxConnection = new SdkSandboxConnection(serviceConnection); 91 92 final String callingPackageName = callingInfo.getPackageName(); 93 String sandboxProcessName = toSandboxProcessName(callingPackageName); 94 try { 95 boolean bound; 96 // For U+, we start the sandbox and then bind to it to prevent restarts. For T, 97 // the sandbox service is directly bound to using BIND_AUTO_CREATE flag which brings 98 // up the sandbox but also restarts it if the sandbox dies when bound. 99 if (SdkLevel.isAtLeastU()) { 100 ComponentName name = 101 mActivityManagerLocal.startSdkSandboxService( 102 intent, 103 callingInfo.getUid(), 104 callingPackageName, 105 sandboxProcessName); 106 if (name == null) { 107 notifyFailedBinding(serviceConnection); 108 return; 109 } 110 bound = 111 mActivityManagerLocal.bindSdkSandboxService( 112 intent, 113 serviceConnection, 114 callingInfo.getUid(), 115 callingInfo.getAppProcessToken(), 116 callingPackageName, 117 sandboxProcessName, 118 0); 119 } else { 120 // Using BIND_AUTO_CREATE will create the sandbox process. 121 bound = 122 mActivityManagerLocal.bindSdkSandboxService( 123 intent, 124 serviceConnection, 125 callingInfo.getUid(), 126 callingPackageName, 127 sandboxProcessName, 128 Context.BIND_AUTO_CREATE); 129 } 130 if (!bound) { 131 mContext.unbindService(serviceConnection); 132 notifyFailedBinding(serviceConnection); 133 return; 134 } 135 } catch (RemoteException e) { 136 notifyFailedBinding(serviceConnection); 137 return; 138 } 139 mAppSdkSandboxConnections.put(callingInfo, sdkSandboxConnection); 140 Log.i(TAG, "Sdk sandbox has been bound"); 141 } 142 } 143 144 // a way to notify manager that binding never happened notifyFailedBinding(ServiceConnection serviceConnection)145 private void notifyFailedBinding(ServiceConnection serviceConnection) { 146 serviceConnection.onNullBinding(null); 147 } 148 149 @Override dump(PrintWriter writer)150 public void dump(PrintWriter writer) { 151 synchronized (mLock) { 152 if (mAppSdkSandboxConnections.size() == 0) { 153 writer.println("mAppSdkSandboxConnections is empty"); 154 } else { 155 writer.print("mAppSdkSandboxConnections size: "); 156 writer.println(mAppSdkSandboxConnections.size()); 157 for (int i = 0; i < mAppSdkSandboxConnections.size(); i++) { 158 CallingInfo callingInfo = mAppSdkSandboxConnections.keyAt(i); 159 SdkSandboxConnection sdkSandboxConnection = 160 mAppSdkSandboxConnections.get(callingInfo); 161 writer.printf( 162 "Sdk sandbox for UID: %s, app package: %s, isConnected: %s Status: %d", 163 callingInfo.getUid(), 164 callingInfo.getPackageName(), 165 Objects.requireNonNull(sdkSandboxConnection).isConnected(), 166 sdkSandboxConnection.getStatus()); 167 writer.println(); 168 } 169 } 170 } 171 } 172 173 @Override unbindService(CallingInfo callingInfo)174 public void unbindService(CallingInfo callingInfo) { 175 synchronized (mLock) { 176 SdkSandboxConnection sandbox = getSdkSandboxConnectionLocked(callingInfo); 177 178 if (sandbox == null) { 179 return; 180 } 181 182 if (sandbox.isBound) { 183 try { 184 mContext.unbindService(sandbox.getServiceConnection()); 185 } catch (Exception e) { 186 // Sandbox has already unbound previously. 187 } 188 sandbox.onUnbind(); 189 Log.i(TAG, "Sdk sandbox for " + callingInfo + " has been unbound"); 190 } 191 } 192 } 193 194 @Override stopSandboxService(CallingInfo callingInfo)195 public void stopSandboxService(CallingInfo callingInfo) { 196 synchronized (mLock) { 197 SdkSandboxConnection sandbox = getSdkSandboxConnectionLocked(callingInfo); 198 199 if (!SdkLevel.isAtLeastU() || sandbox == null || sandbox.getStatus() == NON_EXISTENT) { 200 return; 201 } 202 203 ComponentName componentName = getServiceComponentName(); 204 if (componentName == null) { 205 Log.e(TAG, "Failed to find sdk sandbox service"); 206 return; 207 } 208 final Intent intent = new Intent().setComponent(componentName); 209 final String callingPackageName = callingInfo.getPackageName(); 210 String sandboxProcessName = toSandboxProcessName(callingPackageName); 211 212 mActivityManagerLocal.stopSdkSandboxService( 213 intent, callingInfo.getUid(), callingPackageName, sandboxProcessName); 214 } 215 } 216 217 @Override 218 @Nullable getSdkSandboxServiceForApp(CallingInfo callingInfo)219 public ISdkSandboxService getSdkSandboxServiceForApp(CallingInfo callingInfo) { 220 synchronized (mLock) { 221 SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo); 222 if (connection != null && connection.getStatus() == CREATED) { 223 return connection.getSdkSandboxService(); 224 } 225 } 226 return null; 227 } 228 229 @Override onServiceConnected(CallingInfo callingInfo, @NonNull ISdkSandboxService service)230 public void onServiceConnected(CallingInfo callingInfo, @NonNull ISdkSandboxService service) { 231 synchronized (mLock) { 232 SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo); 233 if (connection != null) { 234 connection.onServiceConnected(service); 235 } 236 } 237 } 238 239 @Override onServiceDisconnected(CallingInfo callingInfo)240 public void onServiceDisconnected(CallingInfo callingInfo) { 241 synchronized (mLock) { 242 SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo); 243 if (connection != null) { 244 connection.onServiceDisconnected(); 245 } 246 } 247 } 248 249 @Override onAppDeath(CallingInfo callingInfo)250 public void onAppDeath(CallingInfo callingInfo) { 251 synchronized (mLock) { 252 mAppSdkSandboxConnections.remove(callingInfo); 253 } 254 } 255 256 @Override onSandboxDeath(CallingInfo callingInfo)257 public void onSandboxDeath(CallingInfo callingInfo) { 258 synchronized (mLock) { 259 SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo); 260 if (connection != null) { 261 connection.onSdkSandboxDeath(); 262 } 263 } 264 } 265 266 @Override getSandboxStatusForApp(CallingInfo callingInfo)267 public int getSandboxStatusForApp(CallingInfo callingInfo) { 268 synchronized (mLock) { 269 SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo); 270 if (connection == null) { 271 return NON_EXISTENT; 272 } else { 273 return connection.getStatus(); 274 } 275 } 276 } 277 278 @Override 279 @NonNull toSandboxProcessName(@onNull String packageName)280 public String toSandboxProcessName(@NonNull String packageName) { 281 return getProcessName(packageName) + SANDBOX_PROCESS_NAME_SUFFIX; 282 } 283 284 @Nullable getServiceComponentName()285 private ComponentName getServiceComponentName() { 286 final Intent intent = new Intent(SdkSandboxManagerLocal.SERVICE_INTERFACE); 287 intent.setPackage(mContext.getPackageManager().getSdkSandboxPackageName()); 288 289 final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 290 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 291 if (resolveInfo == null) { 292 Log.e(TAG, "Failed to find resolveInfo for sdk sandbox service"); 293 return null; 294 } 295 296 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 297 if (serviceInfo == null) { 298 Log.e(TAG, "Failed to find serviceInfo for sdk sandbox service"); 299 return null; 300 } 301 302 return new ComponentName(serviceInfo.packageName, serviceInfo.name); 303 } 304 305 @GuardedBy("mLock") 306 @Nullable getSdkSandboxConnectionLocked(CallingInfo callingInfo)307 private SdkSandboxConnection getSdkSandboxConnectionLocked(CallingInfo callingInfo) { 308 return mAppSdkSandboxConnections.get(callingInfo); 309 } 310 getProcessName(String packageName)311 private String getProcessName(String packageName) { 312 try { 313 return mContext.getPackageManager().getApplicationInfo(packageName, 314 /*flags=*/ 0).processName; 315 } catch (PackageManager.NameNotFoundException e) { 316 Log.e(TAG, packageName + " package not found"); 317 } 318 return packageName; 319 } 320 321 // Represents the connection to an SDK sandbox service. 322 static class SdkSandboxConnection { 323 324 private final Object mLock = new Object(); 325 326 @GuardedBy("mLock") 327 @SandboxStatus 328 private int mStatus = CREATE_PENDING; 329 330 // The connection used to bind and unbind from the SDK sandbox service. 331 private final ServiceConnection mServiceConnection; 332 333 // The binder returned by the SDK sandbox service on connection. 334 @GuardedBy("mLock") 335 @Nullable 336 private ISdkSandboxService mSdkSandboxService = null; 337 338 // Set to true when requested to bind to the SDK sandbox service. It is reset back to false 339 // when unbinding the sandbox service. 340 @GuardedBy("mLock") 341 public boolean isBound = true; 342 SdkSandboxConnection(ServiceConnection serviceConnection)343 SdkSandboxConnection(ServiceConnection serviceConnection) { 344 mServiceConnection = serviceConnection; 345 } 346 347 @SandboxStatus getStatus()348 public int getStatus() { 349 synchronized (mLock) { 350 return mStatus; 351 } 352 } 353 onUnbind()354 public void onUnbind() { 355 synchronized (mLock) { 356 isBound = false; 357 } 358 } 359 onServiceConnected(ISdkSandboxService service)360 public void onServiceConnected(ISdkSandboxService service) { 361 synchronized (mLock) { 362 mStatus = CREATED; 363 mSdkSandboxService = service; 364 } 365 } 366 onServiceDisconnected()367 public void onServiceDisconnected() { 368 synchronized (mLock) { 369 mSdkSandboxService = null; 370 } 371 } 372 onSdkSandboxDeath()373 public void onSdkSandboxDeath() { 374 synchronized (mLock) { 375 // For U+, the sandbox does not restart after dying. 376 if (SdkLevel.isAtLeastU()) { 377 mStatus = NON_EXISTENT; 378 return; 379 } 380 381 if (isBound) { 382 // If the sandbox was bound at the time of death, the system will automatically 383 // restart it. 384 mStatus = CREATE_PENDING; 385 } else { 386 // If the sandbox was not bound at the time of death, the sandbox is dead for 387 // good. 388 mStatus = NON_EXISTENT; 389 } 390 } 391 } 392 393 @Nullable getSdkSandboxService()394 public ISdkSandboxService getSdkSandboxService() { 395 synchronized (mLock) { 396 return mSdkSandboxService; 397 } 398 } 399 getServiceConnection()400 public ServiceConnection getServiceConnection() { 401 return mServiceConnection; 402 } 403 isConnected()404 boolean isConnected() { 405 synchronized (mLock) { 406 return mSdkSandboxService != null; 407 } 408 } 409 } 410 } 411