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.internal.util.CollectionUtils; 35 36 import java.io.PrintWriter; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 42 /** 43 * Manages communication with companion applications via 44 * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to 45 * the services, maintaining the connection (the binding), and invoking callback methods such as 46 * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)} and 47 * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} in the application process. 48 * 49 * <p> 50 * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be 51 * utilized by {@link CompanionDeviceManagerService}): 52 * <ul> 53 * <li> {@link #bindCompanionApplication(int, String, boolean)} 54 * <li> {@link #unbindCompanionApplication(int, String)} 55 * <li> {@link #notifyCompanionApplicationDeviceAppeared(AssociationInfo)} 56 * <li> {@link #notifyCompanionApplicationDeviceDisappeared(AssociationInfo)} 57 * <li> {@link #isCompanionApplicationBound(int, String)} 58 * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} 59 * </ul> 60 * 61 * @see CompanionDeviceService 62 * @see android.companion.ICompanionDeviceService 63 * @see CompanionDeviceServiceConnector 64 */ 65 @SuppressLint("LongLogTag") 66 class CompanionApplicationController { 67 static final boolean DEBUG = false; 68 private static final String TAG = "CompanionDevice_ApplicationController"; 69 70 private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec 71 72 interface Callback { 73 /** 74 * @return {@code true} if should schedule rebinding. 75 * {@code false} if we do not need to rebind. 76 */ onCompanionApplicationBindingDied( @serIdInt int userId, @NonNull String packageName)77 boolean onCompanionApplicationBindingDied( 78 @UserIdInt int userId, @NonNull String packageName); 79 80 /** 81 * Callback after timeout for previously scheduled rebind has passed. 82 */ onRebindCompanionApplicationTimeout( @serIdInt int userId, @NonNull String packageName)83 void onRebindCompanionApplicationTimeout( 84 @UserIdInt int userId, @NonNull String packageName); 85 } 86 87 private final @NonNull Context mContext; 88 private final @NonNull Callback mCallback; 89 private final @NonNull CompanionServicesRegister mCompanionServicesRegister; 90 @GuardedBy("mBoundCompanionApplications") 91 private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>> 92 mBoundCompanionApplications; 93 private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications; 94 CompanionApplicationController(Context context, Callback callback)95 CompanionApplicationController(Context context, Callback callback) { 96 mContext = context; 97 mCallback = callback; 98 mCompanionServicesRegister = new CompanionServicesRegister(); 99 mBoundCompanionApplications = new AndroidPackageMap<>(); 100 mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>(); 101 } 102 onPackagesChanged(@serIdInt int userId)103 void onPackagesChanged(@UserIdInt int userId) { 104 mCompanionServicesRegister.invalidate(userId); 105 } 106 bindCompanionApplication(@serIdInt int userId, @NonNull String packageName, boolean isSelfManaged)107 void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName, 108 boolean isSelfManaged) { 109 if (DEBUG) { 110 Log.i(TAG, "bind() u" + userId + "/" + packageName 111 + " isSelfManaged=" + isSelfManaged); 112 } 113 114 final List<ComponentName> companionServices = 115 mCompanionServicesRegister.forPackage(userId, packageName); 116 if (companionServices.isEmpty()) { 117 Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": " 118 + "eligible CompanionDeviceService not found.\n" 119 + "A CompanionDeviceService should declare an intent-filter for " 120 + "\"android.companion.CompanionDeviceService\" action and require " 121 + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission."); 122 return; 123 } 124 125 final List<CompanionDeviceServiceConnector> serviceConnectors; 126 synchronized (mBoundCompanionApplications) { 127 if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) { 128 if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound."); 129 return; 130 } 131 132 serviceConnectors = CollectionUtils.map(companionServices, componentName -> 133 CompanionDeviceServiceConnector.newInstance(mContext, userId, 134 componentName, isSelfManaged)); 135 mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors); 136 } 137 138 // The first connector in the list is always the primary connector: set a listener to it. 139 serviceConnectors.get(0).setListener(this::onPrimaryServiceBindingDied); 140 141 // Now "bind" all the connectors: the primary one and the rest of them. 142 for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { 143 serviceConnector.connect(); 144 } 145 } 146 unbindCompanionApplication(@serIdInt int userId, @NonNull String packageName)147 void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) { 148 if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName); 149 150 final List<CompanionDeviceServiceConnector> serviceConnectors; 151 synchronized (mBoundCompanionApplications) { 152 serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName); 153 } 154 if (serviceConnectors == null) { 155 if (DEBUG) { 156 Log.e(TAG, "unbindCompanionApplication(): " 157 + "u" + userId + "/" + packageName + " is NOT bound"); 158 Log.d(TAG, "Stacktrace", new Throwable()); 159 } 160 return; 161 } 162 163 for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) { 164 serviceConnector.postUnbind(); 165 } 166 } 167 isCompanionApplicationBound(@serIdInt int userId, @NonNull String packageName)168 boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) { 169 synchronized (mBoundCompanionApplications) { 170 return mBoundCompanionApplications.containsValueForPackage(userId, packageName); 171 } 172 } 173 scheduleRebinding(@serIdInt int userId, @NonNull String packageName)174 private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName) { 175 mScheduledForRebindingCompanionApplications.setValueForPackage(userId, packageName, true); 176 177 Handler.getMain().postDelayed(() -> 178 onRebindingCompanionApplicationTimeout(userId, packageName), REBIND_TIMEOUT); 179 } 180 isRebindingCompanionApplicationScheduled( @serIdInt int userId, @NonNull String packageName)181 boolean isRebindingCompanionApplicationScheduled( 182 @UserIdInt int userId, @NonNull String packageName) { 183 return mScheduledForRebindingCompanionApplications 184 .containsValueForPackage(userId, packageName); 185 } 186 onRebindingCompanionApplicationTimeout( @serIdInt int userId, @NonNull String packageName)187 private void onRebindingCompanionApplicationTimeout( 188 @UserIdInt int userId, @NonNull String packageName) { 189 mScheduledForRebindingCompanionApplications.removePackage(userId, packageName); 190 191 mCallback.onRebindCompanionApplicationTimeout(userId, packageName); 192 } 193 notifyCompanionApplicationDeviceAppeared(AssociationInfo association)194 void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) { 195 final int userId = association.getUserId(); 196 final String packageName = association.getPackageName(); 197 if (DEBUG) { 198 Log.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId 199 + "/" + packageName); 200 } 201 202 final CompanionDeviceServiceConnector primaryServiceConnector = 203 getPrimaryServiceConnector(userId, packageName); 204 if (primaryServiceConnector == null) { 205 if (DEBUG) { 206 Log.e(TAG, "notify_CompanionApplicationDevice_Appeared(): " 207 + "u" + userId + "/" + packageName + " is NOT bound."); 208 Log.d(TAG, "Stacktrace", new Throwable()); 209 } 210 return; 211 } 212 213 primaryServiceConnector.postOnDeviceAppeared(association); 214 } 215 notifyCompanionApplicationDeviceDisappeared(AssociationInfo association)216 void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) { 217 final int userId = association.getUserId(); 218 final String packageName = association.getPackageName(); 219 if (DEBUG) { 220 Log.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId 221 + "/" + packageName); 222 } 223 224 final CompanionDeviceServiceConnector primaryServiceConnector = 225 getPrimaryServiceConnector(userId, packageName); 226 if (primaryServiceConnector == null) { 227 if (DEBUG) { 228 Log.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): " 229 + "u" + userId + "/" + packageName + " is NOT bound."); 230 Log.d(TAG, "Stacktrace", new Throwable()); 231 } 232 return; 233 } 234 235 primaryServiceConnector.postOnDeviceDisappeared(association); 236 } 237 dump(@onNull PrintWriter out)238 void dump(@NonNull PrintWriter out) { 239 out.append("Companion Device Application Controller: \n"); 240 241 synchronized (mBoundCompanionApplications) { 242 out.append(" Bound Companion Applications: "); 243 if (mBoundCompanionApplications.size() == 0) { 244 out.append("<empty>\n"); 245 } else { 246 out.append("\n"); 247 mBoundCompanionApplications.dump(out); 248 } 249 } 250 251 out.append(" Companion Applications Scheduled For Rebinding: "); 252 if (mScheduledForRebindingCompanionApplications.size() == 0) { 253 out.append("<empty>\n"); 254 } else { 255 out.append("\n"); 256 mScheduledForRebindingCompanionApplications.dump(out); 257 } 258 } 259 onPrimaryServiceBindingDied(@serIdInt int userId, @NonNull String packageName)260 private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) { 261 if (DEBUG) Log.i(TAG, "onPrimaryServiceBindingDied() u" + userId + "/" + packageName); 262 263 // First: mark as NOT bound. 264 synchronized (mBoundCompanionApplications) { 265 mBoundCompanionApplications.removePackage(userId, packageName); 266 } 267 268 // Second: invoke callback, schedule rebinding if needed. 269 final boolean shouldScheduleRebind = 270 mCallback.onCompanionApplicationBindingDied(userId, packageName); 271 if (shouldScheduleRebind) { 272 scheduleRebinding(userId, packageName); 273 } 274 } 275 getPrimaryServiceConnector( @serIdInt int userId, @NonNull String packageName)276 private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector( 277 @UserIdInt int userId, @NonNull String packageName) { 278 final List<CompanionDeviceServiceConnector> connectors; 279 synchronized (mBoundCompanionApplications) { 280 connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName); 281 } 282 return connectors != null ? connectors.get(0) : null; 283 } 284 285 private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { 286 @Override forUser( @serIdInt int userId)287 public synchronized @NonNull Map<String, List<ComponentName>> forUser( 288 @UserIdInt int userId) { 289 return super.forUser(userId); 290 } 291 forPackage( @serIdInt int userId, @NonNull String packageName)292 synchronized @NonNull List<ComponentName> forPackage( 293 @UserIdInt int userId, @NonNull String packageName) { 294 return forUser(userId).getOrDefault(packageName, Collections.emptyList()); 295 } 296 invalidate(@serIdInt int userId)297 synchronized void invalidate(@UserIdInt int userId) { 298 remove(userId); 299 } 300 301 @Override create(@serIdInt int userId)302 protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { 303 return PackageUtils.getCompanionServicesForUser(mContext, userId); 304 } 305 } 306 307 /** 308 * Associates an Android package (defined by userId + packageName) with a value of type T. 309 */ 310 private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> { 311 setValueForPackage( @serIdInt int userId, @NonNull String packageName, @NonNull T value)312 void setValueForPackage( 313 @UserIdInt int userId, @NonNull String packageName, @NonNull T value) { 314 Map<String, T> forUser = get(userId); 315 if (forUser == null) { 316 forUser = /* Map<String, T> */ new HashMap(); 317 put(userId, forUser); 318 } 319 320 forUser.put(packageName, value); 321 } 322 containsValueForPackage(@serIdInt int userId, @NonNull String packageName)323 boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) { 324 final Map<String, ?> forUser = get(userId); 325 return forUser != null && forUser.containsKey(packageName); 326 } 327 getValueForPackage(@serIdInt int userId, @NonNull String packageName)328 T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) { 329 final Map<String, T> forUser = get(userId); 330 return forUser != null ? forUser.get(packageName) : null; 331 } 332 removePackage(@serIdInt int userId, @NonNull String packageName)333 T removePackage(@UserIdInt int userId, @NonNull String packageName) { 334 final Map<String, T> forUser = get(userId); 335 if (forUser == null) return null; 336 return forUser.remove(packageName); 337 } 338 dump()339 void dump() { 340 if (size() == 0) { 341 Log.d(TAG, "<empty>"); 342 return; 343 } 344 345 for (int i = 0; i < size(); i++) { 346 final int userId = keyAt(i); 347 final Map<String, T> forUser = get(userId); 348 if (forUser.isEmpty()) { 349 Log.d(TAG, "u" + userId + ": <empty>"); 350 } 351 352 for (Map.Entry<String, T> packageValue : forUser.entrySet()) { 353 final String packageName = packageValue.getKey(); 354 final T value = packageValue.getValue(); 355 Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value); 356 } 357 } 358 } 359 dump(@onNull PrintWriter out)360 private void dump(@NonNull PrintWriter out) { 361 for (int i = 0; i < size(); i++) { 362 final int userId = keyAt(i); 363 final Map<String, T> forUser = get(userId); 364 if (forUser.isEmpty()) { 365 out.append(" u").append(String.valueOf(userId)).append(": <empty>\n"); 366 } 367 368 for (Map.Entry<String, T> packageValue : forUser.entrySet()) { 369 final String packageName = packageValue.getKey(); 370 final T value = packageValue.getValue(); 371 out.append(" u").append(String.valueOf(userId)).append("\\") 372 .append(packageName).append(" -> ") 373 .append(value.toString()).append('\n'); 374 } 375 } 376 } 377 } 378 } 379