• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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