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