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.datatransfer; 18 19 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; 20 import static android.app.PendingIntent.FLAG_IMMUTABLE; 21 import static android.app.PendingIntent.FLAG_ONE_SHOT; 22 import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME; 23 import static android.content.ComponentName.createRelative; 24 25 import static com.android.server.companion.Utils.prepareForIpc; 26 import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE; 27 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.annotation.UserIdInt; 31 import android.app.PendingIntent; 32 import android.companion.AssociationInfo; 33 import android.companion.DeviceNotAssociatedException; 34 import android.companion.IOnMessageReceivedListener; 35 import android.companion.ISystemDataTransferCallback; 36 import android.companion.datatransfer.PermissionSyncRequest; 37 import android.companion.datatransfer.SystemDataTransferRequest; 38 import android.content.ComponentName; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.pm.PackageManagerInternal; 42 import android.os.Binder; 43 import android.os.Bundle; 44 import android.os.Handler; 45 import android.os.IBinder; 46 import android.os.RemoteException; 47 import android.os.ResultReceiver; 48 import android.os.UserHandle; 49 import android.permission.PermissionControllerManager; 50 import android.util.Slog; 51 52 import com.android.server.companion.AssociationStore; 53 import com.android.server.companion.CompanionDeviceManagerService; 54 import com.android.server.companion.PackageUtils; 55 import com.android.server.companion.PermissionsUtils; 56 import com.android.server.companion.transport.CompanionTransportManager; 57 58 import java.util.List; 59 import java.util.concurrent.ExecutorService; 60 import java.util.concurrent.Executors; 61 import java.util.concurrent.Future; 62 import java.util.concurrent.TimeUnit; 63 64 /** 65 * This processor builds user consent intent for a 66 * {@link SystemDataTransferRequest} and processes the request once a channel is 67 * established between the local and remote companion device. 68 */ 69 public class SystemDataTransferProcessor { 70 71 private static final String LOG_TAG = "CDM_SystemDataTransferProcessor"; 72 73 // Values from UI to SystemDataTransferProcessor via ResultReceiver 74 private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED = 0; 75 private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED = 1; 76 private static final String EXTRA_PERMISSION_SYNC_REQUEST = "permission_sync_request"; 77 private static final String EXTRA_COMPANION_DEVICE_NAME = "companion_device_name"; 78 private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER = 79 "system_data_transfer_result_receiver"; 80 private static final ComponentName SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY = 81 createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, 82 ".CompanionDeviceDataTransferActivity"); 83 84 private final Context mContext; 85 private final PackageManagerInternal mPackageManager; 86 private final AssociationStore mAssociationStore; 87 private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; 88 private final CompanionTransportManager mTransportManager; 89 private final PermissionControllerManager mPermissionControllerManager; 90 private final ExecutorService mExecutor; 91 SystemDataTransferProcessor(CompanionDeviceManagerService service, PackageManagerInternal packageManager, AssociationStore associationStore, SystemDataTransferRequestStore systemDataTransferRequestStore, CompanionTransportManager transportManager)92 public SystemDataTransferProcessor(CompanionDeviceManagerService service, 93 PackageManagerInternal packageManager, 94 AssociationStore associationStore, 95 SystemDataTransferRequestStore systemDataTransferRequestStore, 96 CompanionTransportManager transportManager) { 97 mContext = service.getContext(); 98 mPackageManager = packageManager; 99 mAssociationStore = associationStore; 100 mSystemDataTransferRequestStore = systemDataTransferRequestStore; 101 mTransportManager = transportManager; 102 IOnMessageReceivedListener messageListener = new IOnMessageReceivedListener() { 103 @Override 104 public void onMessageReceived(int associationId, byte[] data) throws RemoteException { 105 onReceivePermissionRestore(data); 106 } 107 108 @Override 109 public IBinder asBinder() { 110 return null; 111 } 112 }; 113 mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE, messageListener); 114 mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); 115 mExecutor = Executors.newSingleThreadExecutor(); 116 } 117 118 /** 119 * Resolve the requested association, throwing if the caller doesn't have 120 * adequate permissions. 121 */ resolveAssociation(String packageName, int userId, int associationId)122 private @NonNull AssociationInfo resolveAssociation(String packageName, int userId, 123 int associationId) { 124 AssociationInfo association = mAssociationStore.getAssociationById(associationId); 125 association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association); 126 if (association == null) { 127 throw new DeviceNotAssociatedException("Association " 128 + associationId + " is not associated with the app " + packageName 129 + " for user " + userId); 130 } 131 return association; 132 } 133 134 /** 135 * Build a PendingIntent of permission sync user consent dialog 136 */ buildPermissionTransferUserConsentIntent(String packageName, @UserIdInt int userId, int associationId)137 public PendingIntent buildPermissionTransferUserConsentIntent(String packageName, 138 @UserIdInt int userId, int associationId) { 139 if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) { 140 Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null."); 141 // Auto enable perm sync for the allowlisted packages, but don't override user decision 142 PermissionSyncRequest request = getPermissionSyncRequest(associationId); 143 if (request == null) { 144 PermissionSyncRequest newRequest = new PermissionSyncRequest(associationId); 145 newRequest.setUserConsented(true); 146 mSystemDataTransferRequestStore.writeRequest(userId, newRequest); 147 } 148 return null; 149 } 150 151 final AssociationInfo association = resolveAssociation(packageName, userId, associationId); 152 153 Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId 154 + "] associationId [" + associationId + "]"); 155 156 // Create an internal intent to launch the user consent dialog 157 final Bundle extras = new Bundle(); 158 PermissionSyncRequest request = new PermissionSyncRequest(associationId); 159 request.setUserId(userId); 160 extras.putParcelable(EXTRA_PERMISSION_SYNC_REQUEST, request); 161 extras.putCharSequence(EXTRA_COMPANION_DEVICE_NAME, association.getDisplayName()); 162 extras.putParcelable(EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER, 163 prepareForIpc(mOnSystemDataTransferRequestConfirmationReceiver)); 164 165 final Intent intent = new Intent(); 166 intent.setComponent(SYSTEM_DATA_TRANSFER_REQUEST_APPROVAL_ACTIVITY); 167 intent.putExtras(extras); 168 169 // Create a PendingIntent 170 final long token = Binder.clearCallingIdentity(); 171 try { 172 return PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId, intent, 173 FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, /* options= */ null, 174 UserHandle.CURRENT); 175 } finally { 176 Binder.restoreCallingIdentity(token); 177 } 178 } 179 180 /** 181 * Start system data transfer. It should first try to establish a secure channel and then sync 182 * system data. 183 * 184 * TODO: execute callback when the transfer finishes successfully or with errors. 185 */ startSystemDataTransfer(String packageName, int userId, int associationId, ISystemDataTransferCallback callback)186 public void startSystemDataTransfer(String packageName, int userId, int associationId, 187 ISystemDataTransferCallback callback) { 188 Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName 189 + "] userId [" + userId + "] associationId [" + associationId + "]"); 190 191 final AssociationInfo association = resolveAssociation(packageName, userId, associationId); 192 193 // Check if the request has been consented by the user. 194 PermissionSyncRequest request = getPermissionSyncRequest(associationId); 195 if (request == null || !request.isUserConsented()) { 196 String message = 197 "User " + userId + " hasn't consented permission sync for associationId [" 198 + associationId + "."; 199 Slog.e(LOG_TAG, message); 200 try { 201 callback.onError(message); 202 } catch (RemoteException ignored) { 203 } 204 return; 205 } 206 207 // Start permission sync 208 final long callingIdentityToken = Binder.clearCallingIdentity(); 209 try { 210 // TODO: refactor to work with streams of data 211 mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId), 212 mExecutor, backup -> { 213 Future<?> future = mTransportManager 214 .requestPermissionRestore(associationId, backup); 215 translateFutureToCallback(future, callback); 216 }); 217 } finally { 218 Binder.restoreCallingIdentity(callingIdentityToken); 219 } 220 } 221 222 /** 223 * Enable perm sync for the association 224 */ enablePermissionsSync(int associationId)225 public void enablePermissionsSync(int associationId) { 226 final long callingIdentityToken = Binder.clearCallingIdentity(); 227 try { 228 int userId = mAssociationStore.getAssociationById(associationId).getUserId(); 229 PermissionSyncRequest request = new PermissionSyncRequest(associationId); 230 request.setUserConsented(true); 231 mSystemDataTransferRequestStore.writeRequest(userId, request); 232 } finally { 233 Binder.restoreCallingIdentity(callingIdentityToken); 234 } 235 } 236 237 /** 238 * Disable perm sync for the association 239 */ disablePermissionsSync(int associationId)240 public void disablePermissionsSync(int associationId) { 241 final long callingIdentityToken = Binder.clearCallingIdentity(); 242 try { 243 int userId = mAssociationStore.getAssociationById(associationId).getUserId(); 244 PermissionSyncRequest request = new PermissionSyncRequest(associationId); 245 request.setUserConsented(false); 246 mSystemDataTransferRequestStore.writeRequest(userId, request); 247 } finally { 248 Binder.restoreCallingIdentity(callingIdentityToken); 249 } 250 } 251 252 /** 253 * Get perm sync request for the association. 254 */ 255 @Nullable getPermissionSyncRequest(int associationId)256 public PermissionSyncRequest getPermissionSyncRequest(int associationId) { 257 final long callingIdentityToken = Binder.clearCallingIdentity(); 258 try { 259 int userId = mAssociationStore.getAssociationById(associationId).getUserId(); 260 List<SystemDataTransferRequest> requests = 261 mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, 262 associationId); 263 for (SystemDataTransferRequest request : requests) { 264 if (request instanceof PermissionSyncRequest) { 265 return (PermissionSyncRequest) request; 266 } 267 } 268 return null; 269 } finally { 270 Binder.restoreCallingIdentity(callingIdentityToken); 271 } 272 } 273 274 /** 275 * Remove perm sync request for the association. 276 */ removePermissionSyncRequest(int associationId)277 public void removePermissionSyncRequest(int associationId) { 278 int userId = mAssociationStore.getAssociationById(associationId).getUserId(); 279 mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId); 280 } 281 onReceivePermissionRestore(byte[] message)282 private void onReceivePermissionRestore(byte[] message) { 283 Slog.i(LOG_TAG, "Applying permissions."); 284 // Start applying permissions 285 UserHandle user = mContext.getUser(); 286 final long callingIdentityToken = Binder.clearCallingIdentity(); 287 try { 288 // TODO: refactor to work with streams of data 289 mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup( 290 message, user); 291 } finally { 292 Binder.restoreCallingIdentity(callingIdentityToken); 293 } 294 } 295 translateFutureToCallback(@onNull Future<?> future, @Nullable ISystemDataTransferCallback callback)296 private static void translateFutureToCallback(@NonNull Future<?> future, 297 @Nullable ISystemDataTransferCallback callback) { 298 try { 299 future.get(15, TimeUnit.SECONDS); 300 try { 301 if (callback != null) { 302 callback.onResult(); 303 } 304 } catch (RemoteException ignored) { 305 } 306 } catch (Exception e) { 307 try { 308 if (callback != null) { 309 callback.onError(e.getMessage()); 310 } 311 } catch (RemoteException ignored) { 312 } 313 } 314 } 315 316 private final ResultReceiver mOnSystemDataTransferRequestConfirmationReceiver = 317 new ResultReceiver(Handler.getMain()) { 318 @Override 319 protected void onReceiveResult(int resultCode, Bundle data) { 320 Slog.d(LOG_TAG, "onReceiveResult() code=" + resultCode + ", " 321 + "data=" + data); 322 323 if (resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED 324 || resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED) { 325 final PermissionSyncRequest request = 326 data.getParcelable(EXTRA_PERMISSION_SYNC_REQUEST, 327 PermissionSyncRequest.class); 328 if (request != null) { 329 request.setUserConsented( 330 resultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED); 331 Slog.i(LOG_TAG, "Recording request: " + request); 332 mSystemDataTransferRequestStore.writeRequest(request.getUserId(), 333 request); 334 } 335 336 return; 337 } 338 339 Slog.e(LOG_TAG, "Unknown result code:" + resultCode); 340 } 341 }; 342 } 343