• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.association;
18 
19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
20 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
21 
22 import static com.android.internal.util.CollectionUtils.any;
23 import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
24 
25 import static java.util.concurrent.TimeUnit.DAYS;
26 
27 import android.annotation.NonNull;
28 import android.annotation.SuppressLint;
29 import android.annotation.UserIdInt;
30 import android.app.ActivityManager;
31 import android.companion.AssociationInfo;
32 import android.content.Context;
33 import android.content.pm.PackageManagerInternal;
34 import android.os.Binder;
35 import android.os.SystemProperties;
36 import android.os.UserHandle;
37 import android.util.Slog;
38 
39 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
40 import com.android.server.companion.devicepresence.CompanionAppBinder;
41 import com.android.server.companion.devicepresence.DevicePresenceProcessor;
42 import com.android.server.companion.transport.CompanionTransportManager;
43 
44 /**
45  * This class responsible for disassociation.
46  */
47 @SuppressLint("LongLogTag")
48 public class DisassociationProcessor {
49 
50     public static final String REASON_REVOKED = "revoked";
51     public static final String REASON_SELF_IDLE = "self-idle";
52     public static final String REASON_SHELL = "shell";
53     public static final String REASON_LEGACY = "legacy";
54     public static final String REASON_API = "api";
55     public static final String REASON_PKG_DATA_CLEARED = "pkg-data-cleared";
56 
57     private static final String TAG = "CDM_DisassociationProcessor";
58 
59     private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
60             "debug.cdm.cdmservice.removal_time_window";
61     private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
62 
63     @NonNull
64     private final Context mContext;
65     @NonNull
66     private final AssociationStore mAssociationStore;
67     @NonNull
68     private final PackageManagerInternal mPackageManagerInternal;
69     @NonNull
70     private final DevicePresenceProcessor mDevicePresenceMonitor;
71     @NonNull
72     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
73     @NonNull
74     private final CompanionAppBinder mCompanionAppController;
75     @NonNull
76     private final CompanionTransportManager mTransportManager;
77     private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
78     private final ActivityManager mActivityManager;
79 
DisassociationProcessor(@onNull Context context, @NonNull ActivityManager activityManager, @NonNull AssociationStore associationStore, @NonNull PackageManagerInternal packageManager, @NonNull DevicePresenceProcessor devicePresenceMonitor, @NonNull CompanionAppBinder applicationController, @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore, @NonNull CompanionTransportManager companionTransportManager)80     public DisassociationProcessor(@NonNull Context context,
81             @NonNull ActivityManager activityManager,
82             @NonNull AssociationStore associationStore,
83             @NonNull PackageManagerInternal packageManager,
84             @NonNull DevicePresenceProcessor devicePresenceMonitor,
85             @NonNull CompanionAppBinder applicationController,
86             @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
87             @NonNull CompanionTransportManager companionTransportManager) {
88         mContext = context;
89         mActivityManager = activityManager;
90         mAssociationStore = associationStore;
91         mPackageManagerInternal = packageManager;
92         mOnPackageVisibilityChangeListener =
93                 new OnPackageVisibilityChangeListener();
94         mDevicePresenceMonitor = devicePresenceMonitor;
95         mCompanionAppController = applicationController;
96         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
97         mTransportManager = companionTransportManager;
98     }
99 
100     /**
101      * Disassociate an association by id.
102      */
103     // TODO: also revoke notification access
disassociate(int id, String reason)104     public void disassociate(int id, String reason) {
105         Slog.i(TAG, "Disassociating id=[" + id + "]...");
106 
107         final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
108         final int userId = association.getUserId();
109         final String packageName = association.getPackageName();
110         final String deviceProfile = association.getDeviceProfile();
111 
112         final boolean isRoleInUseByOtherAssociations = deviceProfile != null
113                 && any(mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
114                     it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
115 
116         final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
117         if (packageProcessImportance <= IMPORTANCE_FOREGROUND && deviceProfile != null
118                 && !isRoleInUseByOtherAssociations) {
119             // Need to remove the app from the list of role holders, but the process is visible
120             // to the user at the moment, so we'll need to do it later.
121             Slog.i(TAG, "Cannot disassociate id=[" + id + "] now - process is visible. "
122                     + "Start listening to package importance...");
123 
124             AssociationInfo revokedAssociation = (new AssociationInfo.Builder(
125                     association)).setRevoked(true).build();
126             mAssociationStore.updateAssociation(revokedAssociation);
127             startListening();
128             return;
129         }
130 
131         // Detach transport if exists
132         mTransportManager.detachSystemDataTransport(id);
133 
134         // Association cleanup.
135         mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
136         mAssociationStore.removeAssociation(association.getId(), reason);
137 
138         // If role is not in use by other associations, revoke the role.
139         // Do not need to remove the system role since it was pre-granted by the system.
140         if (!isRoleInUseByOtherAssociations && deviceProfile != null && !deviceProfile.equals(
141                 DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
142             removeRoleHolderForAssociation(mContext, association.getUserId(),
143                     association.getPackageName(), association.getDeviceProfile());
144         }
145 
146         // Unbind the app if needed.
147         final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(id);
148         if (!wasPresent || !association.isNotifyOnDeviceNearby()) {
149             return;
150         }
151         final boolean shouldStayBound = any(
152                 mAssociationStore.getActiveAssociationsByPackage(userId, packageName),
153                 it -> it.isNotifyOnDeviceNearby()
154                         && mDevicePresenceMonitor.isDevicePresent(it.getId()));
155         if (!shouldStayBound) {
156             mCompanionAppController.unbindCompanionApp(userId, packageName);
157         }
158     }
159 
160     /**
161      * @deprecated Use {@link #disassociate(int, String)} instead.
162      */
163     @Deprecated
disassociate(int userId, String packageName, String macAddress)164     public void disassociate(int userId, String packageName, String macAddress) {
165         AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
166                 packageName, macAddress);
167 
168         if (association == null) {
169             throw new IllegalArgumentException(
170                     "Association for mac address=[" + macAddress + "] doesn't exist");
171         }
172 
173         mAssociationStore.getAssociationWithCallerChecks(association.getId());
174 
175         disassociate(association.getId(), REASON_LEGACY);
176     }
177 
178     @SuppressLint("MissingPermission")
getPackageProcessImportance(@serIdInt int userId, @NonNull String packageName)179     private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
180         return Binder.withCleanCallingIdentity(() -> {
181             final int uid =
182                     mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
183             return mActivityManager.getUidImportance(uid);
184         });
185     }
186 
startListening()187     private void startListening() {
188         Slog.i(TAG, "Start listening to uid importance changes...");
189         try {
190             Binder.withCleanCallingIdentity(
191                     () -> mActivityManager.addOnUidImportanceListener(
192                             mOnPackageVisibilityChangeListener,
193                             ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
194         } catch (IllegalArgumentException e) {
195             Slog.e(TAG, "Failed to start listening to uid importance changes.");
196         }
197     }
198 
stopListening()199     private void stopListening() {
200         Slog.i(TAG, "Stop listening to uid importance changes.");
201         try {
202             Binder.withCleanCallingIdentity(() -> mActivityManager.removeOnUidImportanceListener(
203                     mOnPackageVisibilityChangeListener));
204         } catch (IllegalArgumentException e) {
205             Slog.e(TAG, "Failed to stop listening to uid importance changes.");
206         }
207     }
208 
209     /**
210      * Remove idle self-managed associations.
211      */
removeIdleSelfManagedAssociations()212     public void removeIdleSelfManagedAssociations() {
213         Slog.i(TAG, "Removing idle self-managed associations.");
214 
215         final long currentTime = System.currentTimeMillis();
216         long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
217         if (removalWindow <= 0) {
218             // 0 or negative values indicate that the sysprop was never set or should be ignored.
219             removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
220         }
221 
222         for (AssociationInfo association : mAssociationStore.getAssociations()) {
223             if (!association.isSelfManaged()) continue;
224 
225             final boolean isInactive =
226                     currentTime - association.getLastTimeConnectedMs() >= removalWindow;
227             if (!isInactive) continue;
228 
229             final int id = association.getId();
230 
231             Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString()
232                     + "].");
233             disassociate(id, REASON_SELF_IDLE);
234         }
235     }
236 
237     /**
238      * An OnUidImportanceListener class which watches the importance of the packages.
239      * In this class, we ONLY interested in the importance of the running process is greater than
240      * {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE}.
241      *
242      * Lastly remove the role holder for the revoked associations for the same packages.
243      *
244      * @see #disassociate(int, String)
245      */
246     private class OnPackageVisibilityChangeListener implements
247             ActivityManager.OnUidImportanceListener {
248         // This method is called when the importance of a uid (app) changes.
249         // We only care about changes where the app is moving to the background.
250         // (e.g., the app currently is not at the top of the screen that the user
251         // is interacting with.)
252         @Override
onUidImportance(int uid, int importance)253         public void onUidImportance(int uid, int importance) {
254             // Higher importance values indicate the app is less important.
255             // We are only interested when the process importance level
256             // is greater than IMPORTANCE_FOREGROUND.
257             if (importance <= IMPORTANCE_FOREGROUND) {
258                 return;
259             }
260 
261             final String packageName = mPackageManagerInternal.getNameForUid(uid);
262             if (packageName == null) {
263                 // Not interested in this uid.
264                 return;
265             }
266 
267             int userId = UserHandle.getUserId(uid);
268             for (AssociationInfo association : mAssociationStore.getRevokedAssociations(userId,
269                     packageName)) {
270                 disassociate(association.getId(), REASON_REVOKED);
271             }
272 
273             if (mAssociationStore.getRevokedAssociations().isEmpty()) {
274                 stopListening();
275             }
276         }
277     }
278 }
279