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.companion; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SuppressLint; 22 import android.annotation.UserIdInt; 23 import android.companion.AssociationInfo; 24 import android.companion.CompanionDeviceService; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.os.Handler; 28 import android.util.Log; 29 import android.util.Slog; 30 import android.util.SparseArray; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.infra.PerUser; 34 import com.android.server.companion.presence.CompanionDevicePresenceMonitor; 35 36 import java.io.PrintWriter; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * Manages communication with companion applications via 45 * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to 46 * the services, maintaining the connection (the binding), and invoking callback methods such as 47 * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)} and 48 * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} in the application process. 49 * 50 * <p> 51 * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be 52 * utilized by {@link CompanionDeviceManagerService}): 53 * <ul> 54 * <li> {@link #bindCompanionApplication(int, String, boolean)} 55 * <li> {@link #unbindCompanionApplication(int, String)} 56 * <li> {@link #notifyCompanionApplicationDeviceAppeared(AssociationInfo)} 57 * <li> {@link #notifyCompanionApplicationDeviceDisappeared(AssociationInfo)} 58 * <li> {@link #isCompanionApplicationBound(int, String)} 59 * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} 60 * </ul> 61 * 62 * @see CompanionDeviceService 63 * @see android.companion.ICompanionDeviceService 64 * @see CompanionDeviceServiceConnector 65 */ 66 @SuppressLint("LongLogTag") 67 public class CompanionApplicationController { 68 static final boolean DEBUG = false; 69 private static final String TAG = "CDM_CompanionApplicationController"; 70 71 private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec 72 73 private final @NonNull Context mContext; 74 private final @NonNull AssociationStore mAssociationStore; 75 private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor; 76 private final @NonNull CompanionServicesRegister mCompanionServicesRegister; 77 78 @GuardedBy("mBoundCompanionApplications") 79 private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>> 80 mBoundCompanionApplications; 81 @GuardedBy("mScheduledForRebindingCompanionApplications") 82 private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications; 83 CompanionApplicationController(Context context, AssociationStore associationStore, CompanionDevicePresenceMonitor companionDevicePresenceMonitor)84 CompanionApplicationController(Context context, AssociationStore associationStore, 85 CompanionDevicePresenceMonitor companionDevicePresenceMonitor) { 86 mContext = context; 87 mAssociationStore = associationStore; 88 mDevicePresenceMonitor = companionDevicePresenceMonitor; 89 mCompanionServicesRegister = new CompanionServicesRegister(); 90 mBoundCompanionApplications = new AndroidPackageMap<>(); 91 mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>(); 92 } 93 onPackagesChanged(@serIdInt int userId)94 void onPackagesChanged(@UserIdInt int userId) { 95 mCompanionServicesRegister.invalidate(userId); 96 } 97 98 /** 99 * CDM binds to the companion app. 100 */ bindCompanionApplication(@serIdInt int userId, @NonNull String packageName, boolean isSelfManaged)101 public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName, 102 boolean isSelfManaged) { 103 if (DEBUG) { 104 Log.i(TAG, "bind() u" + userId + "/" + packageName 105 + " isSelfManaged=" + isSelfManaged); 106 } 107 108 final List<ComponentName> companionServices = 109 mCompanionServicesRegister.forPackage(userId, packageName); 110 if (companionServices.isEmpty()) { 111 Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": " 112 + "eligible CompanionDeviceService not found.\n" 113 + "A CompanionDeviceService should declare an intent-filter for " 114 + "\"android.companion.CompanionDeviceService\" action and require " 115 + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission."); 116 return; 117 } 118 119 final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>(); 120 synchronized (mBoundCompanionApplications) { 121 if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) { 122 if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound."); 123 return; 124 } 125 126 for (int i = 0; i < companionServices.size(); i++) { 127 boolean isPrimary = i == 0; 128 serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId, 129 companionServices.get(i), isSelfManaged, isPrimary)); 130 } 131 132 mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors); 133 } 134 135 // Set listeners for both Primary and Secondary connectors. 136 for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { 137 serviceConnector.setListener(this::onBinderDied); 138 } 139 140 // Now "bind" all the connectors: the primary one and the rest of them. 141 for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { 142 serviceConnector.connect(); 143 } 144 } 145 146 /** 147 * CDM unbinds the companion app. 148 */ unbindCompanionApplication(@serIdInt int userId, @NonNull String packageName)149 public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) { 150 if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName); 151 152 final List<CompanionDeviceServiceConnector> serviceConnectors; 153 154 synchronized (mBoundCompanionApplications) { 155 serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName); 156 } 157 158 synchronized (mScheduledForRebindingCompanionApplications) { 159 mScheduledForRebindingCompanionApplications.removePackage(userId, packageName); 160 } 161 162 if (serviceConnectors == null) { 163 if (DEBUG) { 164 Log.e(TAG, "unbindCompanionApplication(): " 165 + "u" + userId + "/" + packageName + " is NOT bound"); 166 Log.d(TAG, "Stacktrace", new Throwable()); 167 } 168 return; 169 } 170 171 for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { 172 serviceConnector.postUnbind(); 173 } 174 } 175 176 /** 177 * @return whether the companion application is bound now. 178 */ isCompanionApplicationBound(@serIdInt int userId, @NonNull String packageName)179 public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) { 180 synchronized (mBoundCompanionApplications) { 181 return mBoundCompanionApplications.containsValueForPackage(userId, packageName); 182 } 183 } 184 scheduleRebinding(@serIdInt int userId, @NonNull String packageName, CompanionDeviceServiceConnector serviceConnector)185 private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName, 186 CompanionDeviceServiceConnector serviceConnector) { 187 Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName); 188 189 if (isRebindingCompanionApplicationScheduled(userId, packageName)) { 190 if (DEBUG) { 191 Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping " 192 + serviceConnector.getComponentName()); 193 } 194 return; 195 } 196 197 if (serviceConnector.isPrimary()) { 198 synchronized (mScheduledForRebindingCompanionApplications) { 199 mScheduledForRebindingCompanionApplications.setValueForPackage( 200 userId, packageName, true); 201 } 202 } 203 204 // Rebinding in 10 seconds. 205 Handler.getMain().postDelayed(() -> 206 onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector), 207 REBIND_TIMEOUT); 208 } 209 isRebindingCompanionApplicationScheduled( @serIdInt int userId, @NonNull String packageName)210 private boolean isRebindingCompanionApplicationScheduled( 211 @UserIdInt int userId, @NonNull String packageName) { 212 synchronized (mScheduledForRebindingCompanionApplications) { 213 return mScheduledForRebindingCompanionApplications.containsValueForPackage( 214 userId, packageName); 215 } 216 } 217 onRebindingCompanionApplicationTimeout( @serIdInt int userId, @NonNull String packageName, @NonNull CompanionDeviceServiceConnector serviceConnector)218 private void onRebindingCompanionApplicationTimeout( 219 @UserIdInt int userId, @NonNull String packageName, 220 @NonNull CompanionDeviceServiceConnector serviceConnector) { 221 // Re-mark the application is bound. 222 if (serviceConnector.isPrimary()) { 223 synchronized (mBoundCompanionApplications) { 224 if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) { 225 List<CompanionDeviceServiceConnector> serviceConnectors = 226 Collections.singletonList(serviceConnector); 227 mBoundCompanionApplications.setValueForPackage(userId, packageName, 228 serviceConnectors); 229 } 230 } 231 232 synchronized (mScheduledForRebindingCompanionApplications) { 233 mScheduledForRebindingCompanionApplications.removePackage(userId, packageName); 234 } 235 } 236 237 serviceConnector.connect(); 238 } 239 notifyCompanionApplicationDeviceAppeared(AssociationInfo association)240 void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { 241 final int userId = association.getUserId(); 242 final String packageName = association.getPackageName(); 243 if (DEBUG) { 244 Log.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId 245 + "/" + packageName); 246 } 247 248 final CompanionDeviceServiceConnector primaryServiceConnector = 249 getPrimaryServiceConnector(userId, packageName); 250 if (primaryServiceConnector == null) { 251 if (DEBUG) { 252 Log.e(TAG, "notify_CompanionApplicationDevice_Appeared(): " 253 + "u" + userId + "/" + packageName + " is NOT bound."); 254 Log.d(TAG, "Stacktrace", new Throwable()); 255 } 256 return; 257 } 258 259 Log.i(TAG, "Calling onDeviceAppeared to userId=[" + userId + "] package=[" 260 + packageName + "] associationId=[" + association.getId() + "]"); 261 262 primaryServiceConnector.postOnDeviceAppeared(association); 263 } 264 notifyCompanionApplicationDeviceDisappeared(AssociationInfo association)265 void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { 266 final int userId = association.getUserId(); 267 final String packageName = association.getPackageName(); 268 if (DEBUG) { 269 Log.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId 270 + "/" + packageName); 271 } 272 273 final CompanionDeviceServiceConnector primaryServiceConnector = 274 getPrimaryServiceConnector(userId, packageName); 275 if (primaryServiceConnector == null) { 276 if (DEBUG) { 277 Log.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): " 278 + "u" + userId + "/" + packageName + " is NOT bound."); 279 Log.d(TAG, "Stacktrace", new Throwable()); 280 } 281 return; 282 } 283 284 Log.i(TAG, "Calling onDeviceDisappeared to userId=[" + userId + "] package=[" 285 + packageName + "] associationId=[" + association.getId() + "]"); 286 287 primaryServiceConnector.postOnDeviceDisappeared(association); 288 } 289 dump(@onNull PrintWriter out)290 void dump(@NonNull PrintWriter out) { 291 out.append("Companion Device Application Controller: \n"); 292 293 synchronized (mBoundCompanionApplications) { 294 out.append(" Bound Companion Applications: "); 295 if (mBoundCompanionApplications.size() == 0) { 296 out.append("<empty>\n"); 297 } else { 298 out.append("\n"); 299 mBoundCompanionApplications.dump(out); 300 } 301 } 302 303 out.append(" Companion Applications Scheduled For Rebinding: "); 304 if (mScheduledForRebindingCompanionApplications.size() == 0) { 305 out.append("<empty>\n"); 306 } else { 307 out.append("\n"); 308 mScheduledForRebindingCompanionApplications.dump(out); 309 } 310 } 311 312 /** 313 * Rebinding for Self-Managed secondary services OR Non-Self-Managed services. 314 */ onBinderDied(@serIdInt int userId, @NonNull String packageName, @NonNull CompanionDeviceServiceConnector serviceConnector)315 private void onBinderDied(@UserIdInt int userId, @NonNull String packageName, 316 @NonNull CompanionDeviceServiceConnector serviceConnector) { 317 318 boolean isPrimary = serviceConnector.isPrimary(); 319 Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary); 320 321 // First: Only mark not BOUND for primary service. 322 synchronized (mBoundCompanionApplications) { 323 if (serviceConnector.isPrimary()) { 324 mBoundCompanionApplications.removePackage(userId, packageName); 325 } 326 } 327 328 // Second: schedule rebinding if needed. 329 final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary); 330 331 if (shouldScheduleRebind) { 332 scheduleRebinding(userId, packageName, serviceConnector); 333 } 334 } 335 getPrimaryServiceConnector( @serIdInt int userId, @NonNull String packageName)336 private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector( 337 @UserIdInt int userId, @NonNull String packageName) { 338 final List<CompanionDeviceServiceConnector> connectors; 339 synchronized (mBoundCompanionApplications) { 340 connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName); 341 } 342 return connectors != null ? connectors.get(0) : null; 343 } 344 shouldScheduleRebind(int userId, String packageName, boolean isPrimary)345 private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) { 346 // Make sure do not schedule rebind for the case ServiceConnector still gets callback after 347 // app is uninstalled. 348 boolean stillAssociated = false; 349 // Make sure to clean up the state for all the associations 350 // that associate with this package. 351 boolean shouldScheduleRebind = false; 352 353 for (AssociationInfo ai : 354 mAssociationStore.getAssociationsForPackage(userId, packageName)) { 355 final int associationId = ai.getId(); 356 stillAssociated = true; 357 if (ai.isSelfManaged()) { 358 // Do not rebind if primary one is died for selfManaged application. 359 if (isPrimary 360 && mDevicePresenceMonitor.isDevicePresent(associationId)) { 361 mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId); 362 shouldScheduleRebind = false; 363 } 364 // Do not rebind if both primary and secondary services are died for 365 // selfManaged application. 366 shouldScheduleRebind = isCompanionApplicationBound(userId, packageName); 367 } else if (ai.isNotifyOnDeviceNearby()) { 368 // Always rebind for non-selfManaged devices. 369 shouldScheduleRebind = true; 370 } 371 } 372 373 return stillAssociated && shouldScheduleRebind; 374 } 375 376 private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { 377 @Override forUser( @serIdInt int userId)378 public synchronized @NonNull Map<String, List<ComponentName>> forUser( 379 @UserIdInt int userId) { 380 return super.forUser(userId); 381 } 382 forPackage( @serIdInt int userId, @NonNull String packageName)383 synchronized @NonNull List<ComponentName> forPackage( 384 @UserIdInt int userId, @NonNull String packageName) { 385 return forUser(userId).getOrDefault(packageName, Collections.emptyList()); 386 } 387 invalidate(@serIdInt int userId)388 synchronized void invalidate(@UserIdInt int userId) { 389 remove(userId); 390 } 391 392 @Override create(@serIdInt int userId)393 protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { 394 return PackageUtils.getCompanionServicesForUser(mContext, userId); 395 } 396 } 397 398 /** 399 * Associates an Android package (defined by userId + packageName) with a value of type T. 400 */ 401 private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> { 402 setValueForPackage( @serIdInt int userId, @NonNull String packageName, @NonNull T value)403 void setValueForPackage( 404 @UserIdInt int userId, @NonNull String packageName, @NonNull T value) { 405 Map<String, T> forUser = get(userId); 406 if (forUser == null) { 407 forUser = /* Map<String, T> */ new HashMap(); 408 put(userId, forUser); 409 } 410 411 forUser.put(packageName, value); 412 } 413 containsValueForPackage(@serIdInt int userId, @NonNull String packageName)414 boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) { 415 final Map<String, ?> forUser = get(userId); 416 return forUser != null && forUser.containsKey(packageName); 417 } 418 getValueForPackage(@serIdInt int userId, @NonNull String packageName)419 T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) { 420 final Map<String, T> forUser = get(userId); 421 return forUser != null ? forUser.get(packageName) : null; 422 } 423 removePackage(@serIdInt int userId, @NonNull String packageName)424 T removePackage(@UserIdInt int userId, @NonNull String packageName) { 425 final Map<String, T> forUser = get(userId); 426 if (forUser == null) return null; 427 return forUser.remove(packageName); 428 } 429 dump()430 void dump() { 431 if (size() == 0) { 432 Log.d(TAG, "<empty>"); 433 return; 434 } 435 436 for (int i = 0; i < size(); i++) { 437 final int userId = keyAt(i); 438 final Map<String, T> forUser = get(userId); 439 if (forUser.isEmpty()) { 440 Log.d(TAG, "u" + userId + ": <empty>"); 441 } 442 443 for (Map.Entry<String, T> packageValue : forUser.entrySet()) { 444 final String packageName = packageValue.getKey(); 445 final T value = packageValue.getValue(); 446 Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value); 447 } 448 } 449 } 450 dump(@onNull PrintWriter out)451 private void dump(@NonNull PrintWriter out) { 452 for (int i = 0; i < size(); i++) { 453 final int userId = keyAt(i); 454 final Map<String, T> forUser = get(userId); 455 if (forUser.isEmpty()) { 456 out.append(" u").append(String.valueOf(userId)).append(": <empty>\n"); 457 } 458 459 for (Map.Entry<String, T> packageValue : forUser.entrySet()) { 460 final String packageName = packageValue.getKey(); 461 final T value = packageValue.getValue(); 462 out.append(" u").append(String.valueOf(userId)).append("\\") 463 .append(packageName).append(" -> ") 464 .append(value.toString()).append('\n'); 465 } 466 } 467 } 468 } 469 } 470