1 /* 2 * Copyright (C) 2018 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 android.app.stubs; 18 19 import android.app.Activity; 20 import android.app.ActivityManager; 21 import android.app.ForegroundServiceStartNotAllowedException; 22 import android.app.IActivityManager; 23 import android.app.PendingIntent; 24 import android.content.BroadcastReceiver; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.ServiceConnection; 29 import android.content.pm.PackageManager; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.Parcel; 33 import android.os.RemoteException; 34 import android.text.TextUtils; 35 import android.util.ArrayMap; 36 import android.util.Log; 37 38 import java.util.concurrent.TimeUnit; 39 40 public class CommandReceiver extends BroadcastReceiver { 41 42 private static final String TAG = "CommandReceiver"; 43 44 // Requires flags and targetPackage 45 public static final int COMMAND_BIND_SERVICE = 1; 46 // Requires targetPackage 47 public static final int COMMAND_UNBIND_SERVICE = 2; 48 public static final int COMMAND_START_FOREGROUND_SERVICE = 3; 49 public static final int COMMAND_STOP_FOREGROUND_SERVICE = 4; 50 public static final int COMMAND_START_FOREGROUND_SERVICE_LOCATION = 5; 51 public static final int COMMAND_STOP_FOREGROUND_SERVICE_LOCATION = 6; 52 public static final int COMMAND_START_ALERT_SERVICE = 7; 53 public static final int COMMAND_STOP_ALERT_SERVICE = 8; 54 public static final int COMMAND_SELF_INDUCED_ANR = 9; 55 public static final int COMMAND_START_ACTIVITY = 10; 56 public static final int COMMAND_STOP_ACTIVITY = 11; 57 public static final int COMMAND_CREATE_FGSL_PENDING_INTENT = 12; 58 public static final int COMMAND_SEND_FGSL_PENDING_INTENT = 13; 59 public static final int COMMAND_BIND_FOREGROUND_SERVICE = 14; 60 public static final int COMMAND_START_CHILD_PROCESS = 15; 61 public static final int COMMAND_STOP_CHILD_PROCESS = 16; 62 public static final int COMMAND_WAIT_FOR_CHILD_PROCESS_GONE = 17; 63 public static final int COMMAND_START_SERVICE = 18; 64 public static final int COMMAND_STOP_SERVICE = 19; 65 public static final int COMMAND_START_FOREGROUND_SERVICE_STICKY = 20; 66 public static final int COMMAND_STOP_FOREGROUND_SERVICE_STICKY = 21; 67 public static final int COMMAND_EMPTY = 22; 68 public static final int COMMAND_START_FOREGROUND_SERVICE_SPOOF_PACKAGE_NAME = 23; 69 70 public static final int RESULT_CHILD_PROCESS_STARTED = IBinder.FIRST_CALL_TRANSACTION; 71 public static final int RESULT_CHILD_PROCESS_STOPPED = IBinder.FIRST_CALL_TRANSACTION + 1; 72 public static final int RESULT_CHILD_PROCESS_GONE = IBinder.FIRST_CALL_TRANSACTION + 2; 73 74 public static final String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND"; 75 public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE"; 76 public static final String EXTRA_FLAGS = "android.app.stubs.extra.FLAGS"; 77 public static final String EXTRA_CALLBACK = "android.app.stubs.extra.callback"; 78 public static final String EXTRA_CHILD_CMDLINE = "android.app.stubs.extra.child_cmdline"; 79 public static final String EXTRA_TIMEOUT = "android.app.stubs.extra.child_cmdline"; 80 public static final String EXTRA_MESSENGER = "android.app.stubs.extra.EXTRA_MESSENGER"; 81 82 public static final String SERVICE_NAME = "android.app.stubs.LocalService"; 83 public static final String FG_SERVICE_NAME = "android.app.stubs.LocalForegroundService"; 84 public static final String FG_LOCATION_SERVICE_NAME = 85 "android.app.stubs.LocalForegroundServiceLocation"; 86 public static final String FG_STICKY_SERVICE_NAME = 87 "android.app.stubs.LocalForegroundServiceSticky"; 88 89 public static final String ACTIVITY_NAME = "android.app.stubs.SimpleActivity"; 90 91 private static ArrayMap<String,ServiceConnection> sServiceMap = new ArrayMap<>(); 92 93 // Map a packageName to a Intent that starts an Activity. 94 private static ArrayMap<String, Intent> sActivityIntent = new ArrayMap<>(); 95 96 // Map a packageName to a PendingIntent. 97 private static ArrayMap<String, PendingIntent> sPendingIntent = new ArrayMap<>(); 98 99 /** The child process, started via {@link #COMMAND_START_CHILD_PROCESS} */ 100 private static Process sChildProcess; 101 102 /** 103 * Handle the different types of binding/unbinding requests. 104 * @param context The Context in which the receiver is running. 105 * @param intent The Intent being received. 106 */ 107 @Override onReceive(Context context, Intent intent)108 public void onReceive(Context context, Intent intent) { 109 // Use the application context as the receiver context could be restricted. 110 context = context.getApplicationContext(); 111 int command = intent.getIntExtra(EXTRA_COMMAND, -1); 112 Log.d(TAG + "_" + context.getPackageName(), "Got command " + command + ", intent=" 113 + intent); 114 switch (command) { 115 case COMMAND_BIND_SERVICE: 116 doBindService(context, intent, SERVICE_NAME); 117 break; 118 case COMMAND_UNBIND_SERVICE: 119 doUnbindService(context, intent); 120 break; 121 case COMMAND_START_FOREGROUND_SERVICE: 122 doStartForegroundService(context, intent); 123 break; 124 case COMMAND_START_SERVICE: 125 doStartService(context, intent); 126 break; 127 case COMMAND_STOP_FOREGROUND_SERVICE: 128 case COMMAND_STOP_SERVICE: 129 doStopService(context, intent, FG_SERVICE_NAME); 130 break; 131 case COMMAND_START_FOREGROUND_SERVICE_LOCATION: 132 doStartForegroundServiceWithType(context, intent); 133 break; 134 case COMMAND_STOP_FOREGROUND_SERVICE_LOCATION: 135 doStopService(context, intent, FG_LOCATION_SERVICE_NAME); 136 break; 137 case COMMAND_START_FOREGROUND_SERVICE_STICKY: 138 doStartForegroundServiceSticky(context, intent); 139 break; 140 case COMMAND_STOP_FOREGROUND_SERVICE_STICKY: 141 doStopService(context, intent, FG_STICKY_SERVICE_NAME); 142 break; 143 case COMMAND_START_ALERT_SERVICE: 144 doStartAlertService(context); 145 break; 146 case COMMAND_STOP_ALERT_SERVICE: 147 doStopAlertService(context); 148 break; 149 case COMMAND_SELF_INDUCED_ANR: 150 doSelfInducedAnr(context); 151 break; 152 case COMMAND_START_ACTIVITY: 153 doStartActivity(context, intent); 154 break; 155 case COMMAND_STOP_ACTIVITY: 156 doStopActivity(context, intent); 157 break; 158 case COMMAND_CREATE_FGSL_PENDING_INTENT: 159 doCreateFgslPendingIntent(context, intent); 160 break; 161 case COMMAND_SEND_FGSL_PENDING_INTENT: 162 doSendFgslPendingIntent(context, intent); 163 break; 164 case COMMAND_BIND_FOREGROUND_SERVICE: 165 doBindService(context, intent, FG_LOCATION_SERVICE_NAME); 166 break; 167 case COMMAND_START_CHILD_PROCESS: 168 doStartChildProcess(context, intent); 169 break; 170 case COMMAND_STOP_CHILD_PROCESS: 171 doStopChildProcess(context, intent); 172 break; 173 case COMMAND_WAIT_FOR_CHILD_PROCESS_GONE: 174 doWaitForChildProcessGone(context, intent); 175 break; 176 case COMMAND_EMPTY: 177 break; 178 case COMMAND_START_FOREGROUND_SERVICE_SPOOF_PACKAGE_NAME: 179 doStartForegroundServiceSpoofPackageName(context, intent); 180 break; 181 } 182 } 183 doBindService(Context context, Intent commandIntent, String serviceName)184 private void doBindService(Context context, Intent commandIntent, String serviceName) { 185 String targetPackage = getTargetPackage(commandIntent); 186 int flags = getFlags(commandIntent); 187 188 Intent bindIntent = new Intent(); 189 bindIntent.setComponent(new ComponentName(targetPackage, serviceName)); 190 191 ServiceConnection connection = addServiceConnection(targetPackage); 192 193 context.bindService(bindIntent, connection, flags | Context.BIND_AUTO_CREATE); 194 } 195 doUnbindService(Context context, Intent commandIntent)196 private void doUnbindService(Context context, Intent commandIntent) { 197 String targetPackage = getTargetPackage(commandIntent); 198 context.unbindService(sServiceMap.remove(targetPackage)); 199 } 200 doStartForegroundService(Context context, Intent commandIntent)201 private void doStartForegroundService(Context context, Intent commandIntent) { 202 String targetPackage = getTargetPackage(commandIntent); 203 Intent fgsIntent = new Intent(); 204 fgsIntent.putExtras(commandIntent); 205 fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME)); 206 int command = LocalForegroundService.COMMAND_START_FOREGROUND; 207 fgsIntent.putExtras(LocalForegroundService.newCommand(command)); 208 try { 209 context.startForegroundService(fgsIntent); 210 } catch (ForegroundServiceStartNotAllowedException e) { 211 Log.d(TAG, "startForegroundService gets an " 212 + " ForegroundServiceStartNotAllowedException", e); 213 } 214 } 215 doStartService(Context context, Intent commandIntent)216 private void doStartService(Context context, Intent commandIntent) { 217 String targetPackage = getTargetPackage(commandIntent); 218 Intent fgsIntent = new Intent(); 219 fgsIntent.putExtras(commandIntent); 220 fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME)); 221 context.startService(fgsIntent); 222 } 223 doStartForegroundServiceWithType(Context context, Intent commandIntent)224 private void doStartForegroundServiceWithType(Context context, Intent commandIntent) { 225 String targetPackage = getTargetPackage(commandIntent); 226 Intent fgsIntent = new Intent(); 227 fgsIntent.putExtras(commandIntent); // include the fg service type if any. 228 fgsIntent.setComponent(new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME)); 229 int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE; 230 fgsIntent.putExtras(LocalForegroundService.newCommand(command)); 231 try { 232 context.startForegroundService(fgsIntent); 233 } catch (ForegroundServiceStartNotAllowedException e) { 234 Log.d(TAG, "startForegroundService gets an " 235 + "ForegroundServiceStartNotAllowedException", e); 236 } 237 } 238 doStartForegroundServiceSticky(Context context, Intent commandIntent)239 private void doStartForegroundServiceSticky(Context context, Intent commandIntent) { 240 String targetPackage = getTargetPackage(commandIntent); 241 Intent fgsIntent = new Intent(); 242 fgsIntent.putExtras(commandIntent); 243 fgsIntent.setComponent(new ComponentName(targetPackage, FG_STICKY_SERVICE_NAME)); 244 int command = LocalForegroundService.COMMAND_START_FOREGROUND; 245 fgsIntent.putExtras(LocalForegroundService.newCommand(command)); 246 try { 247 context.startForegroundService(fgsIntent); 248 } catch (ForegroundServiceStartNotAllowedException e) { 249 Log.d(TAG, "startForegroundService gets an " 250 + "ForegroundServiceStartNotAllowedException", e); 251 } 252 } 253 doStopService(Context context, Intent commandIntent, String serviceName)254 private void doStopService(Context context, Intent commandIntent, 255 String serviceName) { 256 String targetPackage = getTargetPackage(commandIntent); 257 Intent fgsIntent = new Intent(); 258 fgsIntent.setComponent(new ComponentName(targetPackage, serviceName)); 259 context.stopService(fgsIntent); 260 } 261 doStartAlertService(Context context)262 private void doStartAlertService(Context context) { 263 Intent intent = new Intent(context, LocalAlertService.class); 264 intent.setAction(LocalAlertService.COMMAND_SHOW_ALERT); 265 context.startService(intent); 266 } 267 doStopAlertService(Context context)268 private void doStopAlertService(Context context) { 269 Intent intent = new Intent(context, LocalAlertService.class); 270 intent.setAction(LocalAlertService.COMMAND_HIDE_ALERT); 271 context.startService(intent); 272 } 273 doSelfInducedAnr(Context context)274 private void doSelfInducedAnr(Context context) { 275 ActivityManager am = context.getSystemService(ActivityManager.class); 276 am.appNotResponding("CTS - self induced"); 277 } 278 doStartActivity(Context context, Intent commandIntent)279 private void doStartActivity(Context context, Intent commandIntent) { 280 String targetPackage = getTargetPackage(commandIntent); 281 Intent activityIntent = new Intent(Intent.ACTION_MAIN); 282 sActivityIntent.put(targetPackage, activityIntent); 283 activityIntent.putExtras(commandIntent); 284 activityIntent.setComponent(new ComponentName(targetPackage, ACTIVITY_NAME)); 285 activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 286 context.startActivity(activityIntent); 287 } 288 doStopActivity(Context context, Intent commandIntent)289 private void doStopActivity(Context context, Intent commandIntent) { 290 String targetPackage = getTargetPackage(commandIntent); 291 Intent activityIntent = sActivityIntent.remove(targetPackage); 292 activityIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 293 activityIntent.putExtra("finish", true); 294 context.startActivity(activityIntent); 295 } 296 doCreateFgslPendingIntent(Context context, Intent commandIntent)297 private void doCreateFgslPendingIntent(Context context, Intent commandIntent) { 298 final String targetPackage = getTargetPackage(commandIntent); 299 final Intent intent = new Intent().setComponent( 300 new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME)); 301 int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE; 302 intent.putExtras(LocalForegroundService.newCommand(command)); 303 final PendingIntent pendingIntent = PendingIntent.getForegroundService(context, 0, 304 intent, PendingIntent.FLAG_IMMUTABLE); 305 sPendingIntent.put(targetPackage, pendingIntent); 306 } 307 doSendFgslPendingIntent(Context context, Intent commandIntent)308 private void doSendFgslPendingIntent(Context context, Intent commandIntent) { 309 final String targetPackage = getTargetPackage(commandIntent); 310 try { 311 ((PendingIntent) sPendingIntent.remove(targetPackage)).send(); 312 } catch (PendingIntent.CanceledException e) { 313 Log.e(TAG, "Caugtht exception:", e); 314 } 315 } 316 doStartChildProcess(Context context, Intent intent)317 private void doStartChildProcess(Context context, Intent intent) { 318 final Bundle extras = intent.getExtras(); 319 final IBinder callback = extras.getBinder(EXTRA_CALLBACK); 320 final String[] cmdline = extras.getStringArray(EXTRA_CHILD_CMDLINE); 321 final Parcel data = Parcel.obtain(); 322 final Parcel reply = Parcel.obtain(); 323 324 try { 325 sChildProcess = Runtime.getRuntime().exec(cmdline); 326 if (sChildProcess != null) { 327 Log.i(TAG, "Forked child: " + sChildProcess); 328 callback.transact(RESULT_CHILD_PROCESS_STARTED, data, reply, 0); 329 } // else the remote will fail with timeout 330 } catch (Exception e) { 331 Log.e(TAG, "Unable to execute command", e); 332 sChildProcess = null; 333 } finally { 334 data.recycle(); 335 reply.recycle(); 336 } 337 } 338 doStopChildProcess(Context context, Intent intent)339 private void doStopChildProcess(Context context, Intent intent) { 340 final Bundle extras = intent.getExtras(); 341 final IBinder callback = extras.getBinder(EXTRA_CALLBACK); 342 final long timeout = extras.getLong(EXTRA_TIMEOUT); 343 waitForChildProcessGone(true, callback, RESULT_CHILD_PROCESS_STOPPED, timeout); 344 } 345 doWaitForChildProcessGone(Context context, Intent intent)346 private void doWaitForChildProcessGone(Context context, Intent intent) { 347 final Bundle extras = intent.getExtras(); 348 final IBinder callback = extras.getBinder(EXTRA_CALLBACK); 349 final long timeout = extras.getLong(EXTRA_TIMEOUT); 350 waitForChildProcessGone(false, callback, RESULT_CHILD_PROCESS_GONE, timeout); 351 } 352 waitForChildProcessGone(final boolean destroy, final IBinder callback, final int transactionCode, final long timeout)353 private static synchronized void waitForChildProcessGone(final boolean destroy, 354 final IBinder callback, final int transactionCode, final long timeout) { 355 if (destroy) { 356 sChildProcess.destroy(); 357 } 358 new Thread(() -> { 359 final Parcel data = Parcel.obtain(); 360 final Parcel reply = Parcel.obtain(); 361 try { 362 if (sChildProcess != null && sChildProcess.isAlive()) { 363 final boolean exit = sChildProcess.waitFor(timeout, TimeUnit.MILLISECONDS); 364 if (exit) { 365 Log.i(TAG, "Child process died: " + sChildProcess); 366 callback.transact(transactionCode, data, reply, 0); 367 } else { 368 Log.w(TAG, "Child process is still alive: " + sChildProcess); 369 } 370 } else { 371 callback.transact(transactionCode, data, reply, 0); 372 } 373 } catch (Exception e) { 374 Log.e(TAG, "Error", e); 375 } finally { 376 data.recycle(); 377 reply.recycle(); 378 } 379 }).start(); 380 } 381 382 /** 383 * Directly call IActivityManager.startService() using a spoofed packageName which is known to 384 * be allowlisted by Android framework to be able to start foreground service 385 * from the background. Framework will disallow the foreground service to start from the 386 * background and a ForegroundServiceStartNotAllowedException will be caught. 387 * @param context 388 * @param commandIntent 389 */ doStartForegroundServiceSpoofPackageName(Context context, Intent commandIntent)390 private void doStartForegroundServiceSpoofPackageName(Context context, Intent commandIntent) { 391 String targetPackage = getTargetPackage(commandIntent); 392 Intent fgsIntent = new Intent(); 393 fgsIntent.putExtras(commandIntent); 394 fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME)); 395 int command = LocalForegroundService.COMMAND_START_FOREGROUND; 396 fgsIntent.putExtras(LocalForegroundService.newCommand(command)); 397 try { 398 final PackageManager pm = context.getPackageManager(); 399 String spoofPackageName = pm.getAttentionServicePackageName(); 400 if (TextUtils.isEmpty(spoofPackageName)) { 401 Log.d(TAG, "getAttentionServicePackageName() returns empty"); 402 spoofPackageName = pm.getSystemCaptionsServicePackageName(); 403 } 404 if (TextUtils.isEmpty(spoofPackageName)) { 405 Log.d(TAG, "getSystemCaptionsServicePackageName() returns empty"); 406 spoofPackageName = "android"; 407 } 408 Log.d(TAG, "spoofPackageName: " + spoofPackageName); 409 final IBinder activityProxy = android.os.ServiceManager.getService("activity"); 410 // Call IActivityManager.startService() directly using a spoofed packageName. 411 IActivityManager.Stub.asInterface(activityProxy).startService( 412 context.getIApplicationThread(), 413 fgsIntent, 414 null, 415 true, 416 spoofPackageName, 417 null, 418 android.os.Process.myUserHandle().getIdentifier() 419 ); 420 } catch (ForegroundServiceStartNotAllowedException e) { 421 Log.d(TAG, "startForegroundService gets an " 422 + " ForegroundServiceStartNotAllowedException", e); 423 } catch (LinkageError e) { 424 // IActivityManager.startService() is a hidden API, access hidden API could get 425 // LinkageError, consider the test as pass if we get LinkageError. 426 Log.d(TAG, "startForegroundService gets an LinkageError", e); 427 } catch (RemoteException e) { 428 Log.d(TAG, "startForegroundService gets an RemoteException", e); 429 } 430 } 431 getTargetPackage(Intent intent)432 private String getTargetPackage(Intent intent) { 433 return intent.getStringExtra(EXTRA_TARGET_PACKAGE); 434 } 435 getFlags(Intent intent)436 private int getFlags(Intent intent) { 437 return intent.getIntExtra(EXTRA_FLAGS, 0); 438 } 439 sendCommand(Context context, int command, String sourcePackage, String targetPackage, int flags, Bundle extras)440 public static void sendCommand(Context context, int command, String sourcePackage, 441 String targetPackage, int flags, Bundle extras) { 442 final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras); 443 Log.d(TAG, "Sending broadcast " + intent); 444 context.sendOrderedBroadcast(intent, null); 445 } 446 sendCommandWithResultReceiver(Context context, int command, String sourcePackage, String targetPackage, int flags, Bundle extras, BroadcastReceiver resultReceiver)447 public static void sendCommandWithResultReceiver(Context context, int command, 448 String sourcePackage, String targetPackage, int flags, Bundle extras, 449 BroadcastReceiver resultReceiver) { 450 final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras); 451 Log.d(TAG, "Sending broadcast with result receiver " + intent); 452 context.sendOrderedBroadcast(intent, null, resultReceiver, null, 453 Activity.RESULT_OK, null, null); 454 } 455 sendCommandWithBroadcastOptions(Context context, int command, String sourcePackage, String targetPackage, int flags, Bundle extras, Bundle broadcastOptions)456 public static void sendCommandWithBroadcastOptions(Context context, int command, 457 String sourcePackage, String targetPackage, int flags, Bundle extras, 458 Bundle broadcastOptions) { 459 final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras); 460 Log.d(TAG, "Sending broadcast with BroadcastOptions " + intent); 461 context.sendOrderedBroadcast(intent, null, broadcastOptions, null, null, 0, null, null); 462 } 463 makeIntent(int command, String sourcePackage, String targetPackage, int flags, Bundle extras)464 private static Intent makeIntent(int command, String sourcePackage, 465 String targetPackage, int flags, Bundle extras) { 466 Intent intent = new Intent(); 467 if (command == COMMAND_BIND_SERVICE || command == COMMAND_START_FOREGROUND_SERVICE 468 || command == COMMAND_STOP_FOREGROUND_SERVICE || command == COMMAND_START_ACTIVITY 469 || command == COMMAND_START_FOREGROUND_SERVICE_LOCATION || command == COMMAND_UNBIND_SERVICE) { 470 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 471 } 472 intent.setComponent(new ComponentName(sourcePackage, "android.app.stubs.CommandReceiver")); 473 intent.putExtra(EXTRA_COMMAND, command); 474 intent.putExtra(EXTRA_FLAGS, flags); 475 intent.putExtra(EXTRA_TARGET_PACKAGE, targetPackage); 476 if (extras != null) { 477 intent.putExtras(extras); 478 } 479 return intent; 480 } 481 addServiceConnection(final String packageName)482 private ServiceConnection addServiceConnection(final String packageName) { 483 ServiceConnection connection = new ServiceConnection() { 484 @Override 485 public void onServiceConnected(ComponentName name, IBinder service) { 486 } 487 488 @Override 489 public void onServiceDisconnected(ComponentName name) { 490 } 491 }; 492 sServiceMap.put(packageName, connection); 493 return connection; 494 } 495 } 496