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 */ onPackagesChanged(@serIdInt int userId)98 public void onPackagesChanged(@UserIdInt int userId) { 99 mCompanionServicesRegister.invalidate(userId); 100 } 101 102 /** 103 * CDM binds to the companion app. 104 */ bindCompanionApp(@serIdInt int userId, @NonNull String packageName, boolean isSelfManaged, CompanionServiceConnector.Listener listener)105 public void bindCompanionApp(@UserIdInt int userId, @NonNull String packageName, 106 boolean isSelfManaged, CompanionServiceConnector.Listener listener) { 107 Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=[" 108 + isSelfManaged + "]..."); 109 110 final List<ComponentName> companionServices = 111 mCompanionServicesRegister.forPackage(userId, packageName); 112 if (companionServices.isEmpty()) { 113 Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": " 114 + "eligible CompanionDeviceService not found.\n" 115 + "A CompanionDeviceService should declare an intent-filter for " 116 + "\"android.companion.CompanionDeviceService\" action and require " 117 + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission."); 118 return; 119 } 120 121 final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>(); 122 synchronized (mBoundCompanionApplications) { 123 if (mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) { 124 Slog.w(TAG, "The package is ALREADY bound."); 125 return; 126 } 127 128 for (int i = 0; i < companionServices.size(); i++) { 129 boolean isPrimary = i == 0; 130 serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId, 131 companionServices.get(i), isSelfManaged, isPrimary)); 132 } 133 134 mBoundCompanionApplications.put(new Pair<>(userId, packageName), serviceConnectors); 135 } 136 137 // Set listeners for both Primary and Secondary connectors. 138 for (CompanionServiceConnector serviceConnector : serviceConnectors) { 139 serviceConnector.setListener(listener); 140 } 141 142 // Now "bind" all the connectors: the primary one and the rest of them. 143 for (CompanionServiceConnector serviceConnector : serviceConnectors) { 144 serviceConnector.connect(); 145 } 146 } 147 148 /** 149 * CDM unbinds the companion app. 150 */ unbindCompanionApp(@serIdInt int userId, @NonNull String packageName)151 public void unbindCompanionApp(@UserIdInt int userId, @NonNull String packageName) { 152 Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]..."); 153 154 final List<CompanionServiceConnector> serviceConnectors; 155 156 synchronized (mBoundCompanionApplications) { 157 serviceConnectors = mBoundCompanionApplications.remove(new Pair<>(userId, packageName)); 158 } 159 160 synchronized (mScheduledForRebindingCompanionApplications) { 161 mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); 162 } 163 164 if (serviceConnectors == null) { 165 Slog.e(TAG, "The package is not bound."); 166 return; 167 } 168 169 for (CompanionServiceConnector serviceConnector : serviceConnectors) { 170 serviceConnector.postUnbind(); 171 } 172 } 173 174 /** 175 * @return whether the companion application is bound now. 176 */ isCompanionApplicationBound(@serIdInt int userId, @NonNull String packageName)177 public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) { 178 synchronized (mBoundCompanionApplications) { 179 return mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName)); 180 } 181 } 182 183 /** 184 * Remove bound apps for package. 185 */ removePackage(int userId, String packageName)186 public void removePackage(int userId, String packageName) { 187 synchronized (mBoundCompanionApplications) { 188 mBoundCompanionApplications.remove(new Pair<>(userId, packageName)); 189 } 190 191 synchronized (mScheduledForRebindingCompanionApplications) { 192 mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); 193 } 194 } 195 196 /** 197 * Schedule rebinding for the package. 198 */ scheduleRebinding(@serIdInt int userId, @NonNull String packageName, CompanionServiceConnector serviceConnector)199 public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName, 200 CompanionServiceConnector serviceConnector) { 201 Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName); 202 203 if (isRebindingCompanionApplicationScheduled(userId, packageName)) { 204 Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping " 205 + serviceConnector.getComponentName()); 206 return; 207 } 208 209 if (serviceConnector.isPrimary()) { 210 synchronized (mScheduledForRebindingCompanionApplications) { 211 mScheduledForRebindingCompanionApplications.add(new Pair<>(userId, packageName)); 212 } 213 } 214 215 // Rebinding in 10 seconds. 216 Handler.getMain().postDelayed(() -> 217 onRebindingCompanionApplicationTimeout(userId, packageName, 218 serviceConnector), 219 REBIND_TIMEOUT); 220 } 221 isRebindingCompanionApplicationScheduled( @serIdInt int userId, @NonNull String packageName)222 private boolean isRebindingCompanionApplicationScheduled( 223 @UserIdInt int userId, @NonNull String packageName) { 224 synchronized (mScheduledForRebindingCompanionApplications) { 225 return mScheduledForRebindingCompanionApplications.contains( 226 new Pair<>(userId, packageName)); 227 } 228 } 229 onRebindingCompanionApplicationTimeout( @serIdInt int userId, @NonNull String packageName, @NonNull CompanionServiceConnector serviceConnector)230 private void onRebindingCompanionApplicationTimeout( 231 @UserIdInt int userId, @NonNull String packageName, 232 @NonNull CompanionServiceConnector serviceConnector) { 233 // Re-mark the application is bound. 234 if (serviceConnector.isPrimary()) { 235 synchronized (mBoundCompanionApplications) { 236 if (!mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) { 237 List<CompanionServiceConnector> serviceConnectors = 238 Collections.singletonList(serviceConnector); 239 mBoundCompanionApplications.put(new Pair<>(userId, packageName), 240 serviceConnectors); 241 } 242 } 243 244 synchronized (mScheduledForRebindingCompanionApplications) { 245 mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName)); 246 } 247 } 248 249 serviceConnector.connect(); 250 } 251 252 /** 253 * Dump bound apps. 254 */ dump(@onNull PrintWriter out)255 public void dump(@NonNull PrintWriter out) { 256 out.append("Companion Device Application Controller: \n"); 257 258 synchronized (mBoundCompanionApplications) { 259 out.append(" Bound Companion Applications: "); 260 if (mBoundCompanionApplications.isEmpty()) { 261 out.append("<empty>\n"); 262 } else { 263 out.append("\n"); 264 for (Map.Entry<Pair<Integer, String>, List<CompanionServiceConnector>> entry : 265 mBoundCompanionApplications.entrySet()) { 266 out.append("<u").append(String.valueOf(entry.getKey().first)).append(", ") 267 .append(entry.getKey().second).append(">"); 268 for (CompanionServiceConnector serviceConnector : entry.getValue()) { 269 out.append(", isPrimary=").append( 270 String.valueOf(serviceConnector.isPrimary())); 271 } 272 } 273 } 274 } 275 276 out.append(" Companion Applications Scheduled For Rebinding: "); 277 synchronized (mScheduledForRebindingCompanionApplications) { 278 if (mScheduledForRebindingCompanionApplications.isEmpty()) { 279 out.append("<empty>\n"); 280 } else { 281 out.append("\n"); 282 for (Pair<Integer, String> app : mScheduledForRebindingCompanionApplications) { 283 out.append("<u").append(String.valueOf(app.first)).append(", ") 284 .append(app.second).append(">"); 285 } 286 } 287 } 288 } 289 290 @Nullable getPrimaryServiceConnector( @serIdInt int userId, @NonNull String packageName)291 CompanionServiceConnector getPrimaryServiceConnector( 292 @UserIdInt int userId, @NonNull String packageName) { 293 final List<CompanionServiceConnector> connectors; 294 synchronized (mBoundCompanionApplications) { 295 connectors = mBoundCompanionApplications.get(new Pair<>(userId, packageName)); 296 } 297 return connectors != null ? connectors.get(0) : null; 298 } 299 300 private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> { 301 @Override forUser( @serIdInt int userId)302 public synchronized @NonNull Map<String, List<ComponentName>> forUser( 303 @UserIdInt int userId) { 304 return super.forUser(userId); 305 } 306 forPackage( @serIdInt int userId, @NonNull String packageName)307 synchronized @NonNull List<ComponentName> forPackage( 308 @UserIdInt int userId, @NonNull String packageName) { 309 return forUser(userId).getOrDefault(packageName, Collections.emptyList()); 310 } 311 invalidate(@serIdInt int userId)312 synchronized void invalidate(@UserIdInt int userId) { 313 remove(userId); 314 } 315 316 @Override create(@serIdInt int userId)317 protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) { 318 return PackageUtils.getCompanionServicesForUser(mContext, userId); 319 } 320 } 321 } 322