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.devicepresence; 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.companion.DevicePresenceEvent; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.os.Handler; 29 import android.util.Pair; 30 import android.util.Slog; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.infra.PerUser; 34 import com.android.server.companion.CompanionDeviceManagerService; 35 import com.android.server.companion.utils.PackageUtils; 36 37 import java.io.PrintWriter; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 46 /** 47 * Manages communication with companion applications via 48 * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to 49 * the services, maintaining the connection (the binding), and invoking callback methods such as 50 * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)}, 51 * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and 52 * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the 53 * application process. 54 * 55 * <p> 56 * The following is the list of the APIs provided by {@link CompanionAppBinder} (to be 57 * utilized by {@link CompanionDeviceManagerService}): 58 * <ul> 59 * <li> {@link #bindCompanionApp(int, String, boolean, CompanionServiceConnector.Listener)} 60 * <li> {@link #unbindCompanionApp(int, String)} 61 * <li> {@link #isCompanionApplicationBound(int, String)} 62 * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)} 63 * </ul> 64 * 65 * @see CompanionDeviceService 66 * @see android.companion.ICompanionDeviceService 67 * @see CompanionServiceConnector 68 */ 69 @SuppressLint("LongLogTag") 70 public class CompanionAppBinder { 71 private static final String TAG = "CDM_CompanionAppBinder"; 72 73 private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec 74 75 @NonNull 76 private final Context mContext; 77 @NonNull 78 private final CompanionServicesRegister mCompanionServicesRegister; 79 80 @NonNull 81 @GuardedBy("mBoundCompanionApplications") 82 private final Map<Pair<Integer, String>, List<CompanionServiceConnector>> 83 mBoundCompanionApplications; 84 @NonNull 85 @GuardedBy("mScheduledForRebindingCompanionApplications") 86 private final Set<Pair<Integer, String>> mScheduledForRebindingCompanionApplications; 87 CompanionAppBinder(@onNull Context context)88 public CompanionAppBinder(@NonNull Context context) { 89 mContext = context; 90 mCompanionServicesRegister = new CompanionServicesRegister(); 91 mBoundCompanionApplications = new HashMap<>(); 92 mScheduledForRebindingCompanionApplications = new HashSet<>(); 93 } 94 95 /** 96 * On package changed. 97 */ onPackageChanged(@serIdInt int userId)98 public void onPackageChanged(@UserIdInt int userId) { 99 // Note: To invalidate the user space for simplicity. We could alternatively manage each 100 // package, but that would easily cause errors if one case is mis-handled. 101 mCompanionServicesRegister.invalidate(userId); 102 } 103 104 /** 105 * CDM binds to the companion app. 106 */ bindCompanionApp(@serIdInt int userId, @NonNull String packageName, boolean isSelfManaged, CompanionServiceConnector.Listener listener)107 public void bindCompanionApp(@UserIdInt int userId, @NonNull String packageName, 108 boolean isSelfManaged, CompanionServiceConnector.Listener listener) { 109 Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=[" 110 + isSelfManaged + "]..."); 111 112 final List<ComponentName> companionServices = 113 mCompanionServicesRegister.forPackage(userId, packageName); 114 if (companionServices.isEmpty()) { 115 Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": " 116 + "eligible CompanionDeviceService not found.\n" 117 + "A CompanionDeviceService should declare an intent-filter for " 118 + "\"android.companion.CompanionDeviceService\" action and require " 119 + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission."); 120 return; 121 } 122 123 final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>(); 124 synchronized (mBoundCompanionApplications) { 125 if (mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) { 126 Slog.w(TAG, "The package is ALREADY bound."); 127 return; 128 } 129 130 for (int i = 0; i < companionServices.size(); i++) { 131 boolean isPrimary = i == 0; 132 serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId, 133 companionServices.get(i), isSelfManaged, isPrimary)); 134 } 135 136 mBoundCompanionApplications.put(new Pair<>(userId, packageName), serviceConnectors); 137 } 138 139 // Set listeners for both Primary and Secondary connectors. 140 for (CompanionServiceConnector serviceConnector : serviceConnectors) { 141 serviceConnector.setListener(listener); 142 } 143 144 // Now "bind" all the connectors: the primary one and the rest of them. 145 for (CompanionServiceConnector serviceConnector : serviceConnectors) { 146 serviceConnector.connect(); 147 } 148 } 149 150 /** 151 * CDM unbinds the companion app. 152 */ unbindCompanionApp(@serIdInt int userId, @NonNull String packageName)153 public void unbindCompanionApp(@UserIdInt int userId, @NonNull String packageName) { 154 Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]..."); 155 156 final List<CompanionServiceConnector> serviceConnectors; 157 158 synchronized (mBoundCompanionApplications) { 159 serviceConnectors = mBoundCompanionApplications.remove(new Pair<>(userId, packageName)); 160 } 161 162 synchronized (mScheduledForRebindingCompanionApplications) { 163 mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); 164 } 165 166 if (serviceConnectors == null) { 167 Slog.e(TAG, "The package is not bound."); 168 return; 169 } 170 171 for (CompanionServiceConnector 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.containsKey(new Pair<>(userId, packageName)); 182 } 183 } 184 185 /** 186 * Remove bound apps for package. 187 */ removePackage(int userId, String packageName)188 public void removePackage(int userId, String packageName) { 189 synchronized (mBoundCompanionApplications) { 190 mBoundCompanionApplications.remove(new Pair<>(userId, packageName)); 191 } 192 193 synchronized (mScheduledForRebindingCompanionApplications) { 194 mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); 195 } 196 } 197 198 /** 199 * Schedule rebinding for the package. 200 */ scheduleRebinding(@serIdInt int userId, @NonNull String packageName, CompanionServiceConnector serviceConnector)201 public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName, 202 CompanionServiceConnector serviceConnector) { 203 Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName); 204 205 if (isRebindingCompanionApplicationScheduled(userId, packageName)) { 206 Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping " 207 + serviceConnector.getComponentName()); 208 return; 209 } 210 211 if (serviceConnector.isPrimary()) { 212 synchronized (mScheduledForRebindingCompanionApplications) { 213 mScheduledForRebindingCompanionApplications.add(new Pair<>(userId, packageName)); 214 } 215 } 216 217 // Rebinding in 10 seconds. 218 Handler.getMain().postDelayed(() -> 219 onRebindingCompanionApplicationTimeout(userId, packageName, 220 serviceConnector), 221 REBIND_TIMEOUT); 222 } 223 isRebindingCompanionApplicationScheduled( @serIdInt int userId, @NonNull String packageName)224 private boolean isRebindingCompanionApplicationScheduled( 225 @UserIdInt int userId, @NonNull String packageName) { 226 synchronized (mScheduledForRebindingCompanionApplications) { 227 return mScheduledForRebindingCompanionApplications.contains( 228 new Pair<>(userId, packageName)); 229 } 230 } 231 onRebindingCompanionApplicationTimeout( @serIdInt int userId, @NonNull String packageName, @NonNull CompanionServiceConnector serviceConnector)232 private void onRebindingCompanionApplicationTimeout( 233 @UserIdInt int userId, @NonNull String packageName, 234 @NonNull CompanionServiceConnector serviceConnector) { 235 // Re-mark the application is bound. 236 if (serviceConnector.isPrimary()) { 237 synchronized (mBoundCompanionApplications) { 238 if (!mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) { 239 List<CompanionServiceConnector> serviceConnectors = 240 Collections.singletonList(serviceConnector); 241 mBoundCompanionApplications.put(new Pair<>(userId, packageName), 242 serviceConnectors); 243 } 244 } 245 246 synchronized (mScheduledForRebindingCompanionApplications) { 247 mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); 248 } 249 } 250 251 serviceConnector.connect(); 252 } 253 254 /** 255 * Dump bound apps. 256 */ dump(@onNull PrintWriter out)257 public void dump(@NonNull PrintWriter out) { 258 out.append("Companion Device Application Controller: \n"); 259 260 synchronized (mBoundCompanionApplications) { 261 out.append(" Bound Companion Applications: "); 262 if (mBoundCompanionApplications.isEmpty()) { 263 out.append("<empty>\n"); 264 } else { 265 out.append("\n"); 266 for (Map.Entry<Pair<Integer, String>, List<CompanionServiceConnector>> entry : 267 mBoundCompanionApplications.entrySet()) { 268 out.append("<u").append(String.valueOf(entry.getKey().first)).append(", ") 269 .append(entry.getKey().second).append(">"); 270 for (CompanionServiceConnector serviceConnector : entry.getValue()) { 271 out.append(", isPrimary=").append( 272 String.valueOf(serviceConnector.isPrimary())); 273 } 274 } 275 } 276 } 277 278 out.append(" Companion Applications Scheduled For Rebinding: "); 279 synchronized (mScheduledForRebindingCompanionApplications) { 280 if (mScheduledForRebindingCompanionApplications.isEmpty()) { 281 out.append("<empty>\n"); 282 } else { 283 out.append("\n"); 284 for (Pair<Integer, String> app : mScheduledForRebindingCompanionApplications) { 285 out.append("<u").append(String.valueOf(app.first)).append(", ") 286 .append(app.second).append(">"); 287 } 288 } 289 } 290 } 291 292 @Nullable getPrimaryServiceConnector( @serIdInt int userId, @NonNull String packageName)293 CompanionServiceConnector getPrimaryServiceConnector( 294 @UserIdInt int userId, @NonNull String packageName) { 295 final List<CompanionServiceConnector> connectors; 296 synchronized (mBoundCompanionApplications) { 297 connectors = mBoundCompanionApplications.get(new Pair<>(userId, packageName)); 298 } 299 return connectors != null ? connectors.get(0) : null; 300 } 301 302 private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { 303 @Override 304 @NonNull forUser( @serIdInt int userId)305 public synchronized Map<String, List<ComponentName>> forUser( 306 @UserIdInt int userId) { 307 return super.forUser(userId); 308 } 309 310 @NonNull forPackage( @serIdInt int userId, @NonNull String packageName)311 synchronized List<ComponentName> forPackage( 312 @UserIdInt int userId, @NonNull String packageName) { 313 return forUser(userId).getOrDefault(packageName, Collections.emptyList()); 314 } 315 invalidate(@serIdInt int userId)316 synchronized void invalidate(@UserIdInt int userId) { 317 remove(userId); 318 } 319 320 @Override 321 @NonNull create(@serIdInt int userId)322 protected final Map<String, List<ComponentName>> create(@UserIdInt int userId) { 323 return PackageUtils.getCompanionServicesForUser(mContext, userId); 324 } 325 } 326 } 327