• 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;
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