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