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.backup.transport; 18 19 import android.annotation.Nullable; 20 import android.app.backup.BackupTransport; 21 import android.app.backup.IBackupManagerMonitor; 22 import android.app.backup.RestoreDescription; 23 import android.app.backup.RestoreSet; 24 import android.content.Intent; 25 import android.content.pm.PackageInfo; 26 import android.os.IBinder; 27 import android.os.ParcelFileDescriptor; 28 import android.os.RemoteException; 29 import android.util.Slog; 30 31 import com.android.internal.backup.IBackupTransport; 32 import com.android.internal.infra.AndroidFuture; 33 import com.android.server.backup.BackupAndRestoreFeatureFlags; 34 35 import java.util.ArrayDeque; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.Queue; 39 import java.util.Set; 40 import java.util.concurrent.CancellationException; 41 import java.util.concurrent.ExecutionException; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.TimeoutException; 44 45 /** 46 * Client to {@link com.android.internal.backup.IBackupTransport}. Manages the call to the remote 47 * transport service and delivers the results. 48 */ 49 public class BackupTransportClient { 50 private static final String TAG = "BackupTransportClient"; 51 52 private final IBackupTransport mTransportBinder; 53 private final TransportStatusCallbackPool mCallbackPool; 54 private final TransportFutures mTransportFutures; 55 BackupTransportClient(IBackupTransport transportBinder)56 BackupTransportClient(IBackupTransport transportBinder) { 57 mTransportBinder = transportBinder; 58 mCallbackPool = new TransportStatusCallbackPool(); 59 mTransportFutures = new TransportFutures(); 60 } 61 62 /** 63 * See {@link IBackupTransport#name()}. 64 */ name()65 public String name() throws RemoteException { 66 AndroidFuture<String> resultFuture = mTransportFutures.newFuture(); 67 mTransportBinder.name(resultFuture); 68 return getFutureResult(resultFuture); 69 } 70 71 /** 72 * See {@link IBackupTransport#configurationIntent()} 73 */ configurationIntent()74 public Intent configurationIntent() throws RemoteException { 75 AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture(); 76 mTransportBinder.configurationIntent(resultFuture); 77 return getFutureResult(resultFuture); 78 } 79 80 /** 81 * See {@link IBackupTransport#currentDestinationString()} 82 */ currentDestinationString()83 public String currentDestinationString() throws RemoteException { 84 AndroidFuture<String> resultFuture = mTransportFutures.newFuture(); 85 mTransportBinder.currentDestinationString(resultFuture); 86 return getFutureResult(resultFuture); 87 } 88 89 /** 90 * See {@link IBackupTransport#dataManagementIntent()} 91 */ dataManagementIntent()92 public Intent dataManagementIntent() throws RemoteException { 93 AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture(); 94 mTransportBinder.dataManagementIntent(resultFuture); 95 return getFutureResult(resultFuture); 96 } 97 98 /** 99 * See {@link IBackupTransport#dataManagementIntentLabel()} 100 */ 101 @Nullable dataManagementIntentLabel()102 public CharSequence dataManagementIntentLabel() throws RemoteException { 103 AndroidFuture<CharSequence> resultFuture = mTransportFutures.newFuture(); 104 mTransportBinder.dataManagementIntentLabel(resultFuture); 105 return getFutureResult(resultFuture); 106 } 107 108 /** 109 * See {@link IBackupTransport#transportDirName()} 110 */ transportDirName()111 public String transportDirName() throws RemoteException { 112 AndroidFuture<String> resultFuture = mTransportFutures.newFuture(); 113 mTransportBinder.transportDirName(resultFuture); 114 return getFutureResult(resultFuture); 115 } 116 117 /** 118 * See {@link IBackupTransport#initializeDevice()} 119 */ initializeDevice()120 public int initializeDevice() throws RemoteException { 121 TransportStatusCallback callback = mCallbackPool.acquire(); 122 try { 123 mTransportBinder.initializeDevice(callback); 124 return callback.getOperationStatus(); 125 } finally { 126 mCallbackPool.recycle(callback); 127 } 128 } 129 130 /** 131 * See {@link IBackupTransport#clearBackupData(PackageInfo)} 132 */ clearBackupData(PackageInfo packageInfo)133 public int clearBackupData(PackageInfo packageInfo) throws RemoteException { 134 TransportStatusCallback callback = mCallbackPool.acquire(); 135 try { 136 mTransportBinder.clearBackupData(packageInfo, callback); 137 return callback.getOperationStatus(); 138 } finally { 139 mCallbackPool.recycle(callback); 140 } 141 } 142 143 /** 144 * See {@link IBackupTransport#finishBackup()} 145 */ finishBackup()146 public int finishBackup() throws RemoteException { 147 TransportStatusCallback callback = mCallbackPool.acquire(); 148 try { 149 mTransportBinder.finishBackup(callback); 150 return callback.getOperationStatus(); 151 } finally { 152 mCallbackPool.recycle(callback); 153 } 154 } 155 156 /** 157 * See {@link IBackupTransport#requestBackupTime()} 158 */ requestBackupTime()159 public long requestBackupTime() throws RemoteException { 160 AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); 161 mTransportBinder.requestBackupTime(resultFuture); 162 Long result = getFutureResult(resultFuture); 163 return result == null ? BackupTransport.TRANSPORT_ERROR : result; 164 } 165 166 /** 167 * See {@link IBackupTransport#performBackup(PackageInfo, ParcelFileDescriptor, int)} 168 */ performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)169 public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) 170 throws RemoteException { 171 TransportStatusCallback callback = mCallbackPool.acquire(); 172 try { 173 mTransportBinder.performBackup(packageInfo, inFd, flags, callback); 174 return callback.getOperationStatus(); 175 } finally { 176 mCallbackPool.recycle(callback); 177 } 178 } 179 180 /** 181 * See {@link IBackupTransport#getAvailableRestoreSets()} 182 */ getAvailableRestoreSets()183 public List<RestoreSet> getAvailableRestoreSets() throws RemoteException { 184 AndroidFuture<List<RestoreSet>> resultFuture = mTransportFutures.newFuture(); 185 mTransportBinder.getAvailableRestoreSets(resultFuture); 186 List<RestoreSet> result = getFutureResult(resultFuture); 187 return result; 188 } 189 190 /** 191 * See {@link IBackupTransport#getCurrentRestoreSet()} 192 */ getCurrentRestoreSet()193 public long getCurrentRestoreSet() throws RemoteException { 194 AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); 195 mTransportBinder.getCurrentRestoreSet(resultFuture); 196 Long result = getFutureResult(resultFuture); 197 return result == null ? BackupTransport.TRANSPORT_ERROR : result; 198 } 199 200 /** 201 * See {@link IBackupTransport#startRestore(long, PackageInfo[])} 202 */ startRestore(long token, PackageInfo[] packages)203 public int startRestore(long token, PackageInfo[] packages) throws RemoteException { 204 TransportStatusCallback callback = mCallbackPool.acquire(); 205 try { 206 mTransportBinder.startRestore(token, packages, callback); 207 return callback.getOperationStatus(); 208 } finally { 209 mCallbackPool.recycle(callback); 210 } 211 } 212 213 /** 214 * See {@link IBackupTransport#nextRestorePackage()} 215 */ nextRestorePackage()216 public RestoreDescription nextRestorePackage() throws RemoteException { 217 AndroidFuture<RestoreDescription> resultFuture = mTransportFutures.newFuture(); 218 mTransportBinder.nextRestorePackage(resultFuture); 219 return getFutureResult(resultFuture); 220 } 221 222 /** 223 * See {@link IBackupTransport#getRestoreData(ParcelFileDescriptor)} 224 */ getRestoreData(ParcelFileDescriptor outFd)225 public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { 226 TransportStatusCallback callback = mCallbackPool.acquire(); 227 try { 228 mTransportBinder.getRestoreData(outFd, callback); 229 return callback.getOperationStatus(); 230 } finally { 231 mCallbackPool.recycle(callback); 232 } 233 } 234 235 /** 236 * See {@link IBackupTransport#finishRestore()} 237 */ finishRestore()238 public void finishRestore() throws RemoteException { 239 TransportStatusCallback callback = mCallbackPool.acquire(); 240 try { 241 mTransportBinder.finishRestore(callback); 242 callback.getOperationStatus(); 243 } finally { 244 mCallbackPool.recycle(callback); 245 } 246 } 247 248 /** 249 * See {@link IBackupTransport#requestFullBackupTime()} 250 */ requestFullBackupTime()251 public long requestFullBackupTime() throws RemoteException { 252 AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); 253 mTransportBinder.requestFullBackupTime(resultFuture); 254 Long result = getFutureResult(resultFuture); 255 return result == null ? BackupTransport.TRANSPORT_ERROR : result; 256 } 257 258 /** 259 * See {@link IBackupTransport#performFullBackup(PackageInfo, ParcelFileDescriptor, int)} 260 */ performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, int flags)261 public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, 262 int flags) throws RemoteException { 263 TransportStatusCallback callback = mCallbackPool.acquire(); 264 try { 265 mTransportBinder.performFullBackup(targetPackage, socket, flags, callback); 266 return callback.getOperationStatus(); 267 } finally { 268 mCallbackPool.recycle(callback); 269 } 270 } 271 272 /** 273 * See {@link IBackupTransport#checkFullBackupSize(long)} 274 */ checkFullBackupSize(long size)275 public int checkFullBackupSize(long size) throws RemoteException { 276 TransportStatusCallback callback = mCallbackPool.acquire(); 277 try { 278 mTransportBinder.checkFullBackupSize(size, callback); 279 return callback.getOperationStatus(); 280 } finally { 281 mCallbackPool.recycle(callback); 282 } 283 } 284 285 /** 286 * See {@link IBackupTransport#sendBackupData(int)} 287 */ sendBackupData(int numBytes)288 public int sendBackupData(int numBytes) throws RemoteException { 289 TransportStatusCallback callback = mCallbackPool.acquire(); 290 mTransportBinder.sendBackupData(numBytes, callback); 291 try { 292 return callback.getOperationStatus(); 293 } finally { 294 mCallbackPool.recycle(callback); 295 } 296 } 297 298 /** 299 * See {@link IBackupTransport#cancelFullBackup()} 300 */ cancelFullBackup()301 public void cancelFullBackup() throws RemoteException { 302 TransportStatusCallback callback = mCallbackPool.acquire(); 303 try { 304 mTransportBinder.cancelFullBackup(callback); 305 callback.getOperationStatus(); 306 } finally { 307 mCallbackPool.recycle(callback); 308 } 309 } 310 311 /** 312 * See {@link IBackupTransport#isAppEligibleForBackup(PackageInfo, boolean)} 313 */ isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)314 public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) 315 throws RemoteException { 316 AndroidFuture<Boolean> resultFuture = mTransportFutures.newFuture(); 317 mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup, resultFuture); 318 Boolean result = getFutureResult(resultFuture); 319 return result != null && result; 320 } 321 322 /** 323 * See {@link IBackupTransport#getBackupQuota(String, boolean)} 324 */ getBackupQuota(String packageName, boolean isFullBackup)325 public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException { 326 AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); 327 mTransportBinder.getBackupQuota(packageName, isFullBackup, resultFuture); 328 Long result = getFutureResult(resultFuture); 329 return result == null ? BackupTransport.TRANSPORT_ERROR : result; 330 } 331 332 /** 333 * See {@link IBackupTransport#getNextFullRestoreDataChunk(ParcelFileDescriptor)} 334 */ getNextFullRestoreDataChunk(ParcelFileDescriptor socket)335 public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException { 336 TransportStatusCallback callback = mCallbackPool.acquire(); 337 try { 338 mTransportBinder.getNextFullRestoreDataChunk(socket, callback); 339 return callback.getOperationStatus(); 340 } finally { 341 mCallbackPool.recycle(callback); 342 } 343 } 344 345 /** 346 * See {@link IBackupTransport#abortFullRestore()} 347 */ abortFullRestore()348 public int abortFullRestore() throws RemoteException { 349 TransportStatusCallback callback = mCallbackPool.acquire(); 350 try { 351 mTransportBinder.abortFullRestore(callback); 352 return callback.getOperationStatus(); 353 } finally { 354 mCallbackPool.recycle(callback); 355 } 356 } 357 358 /** 359 * See {@link IBackupTransport#getTransportFlags()} 360 */ getTransportFlags()361 public int getTransportFlags() throws RemoteException { 362 AndroidFuture<Integer> resultFuture = mTransportFutures.newFuture(); 363 mTransportBinder.getTransportFlags(resultFuture); 364 Integer result = getFutureResult(resultFuture); 365 return result == null ? BackupTransport.TRANSPORT_ERROR : result; 366 } 367 368 /** 369 * See {@link IBackupTransport#getBackupManagerMonitor()} 370 */ getBackupManagerMonitor()371 public IBackupManagerMonitor getBackupManagerMonitor() throws RemoteException { 372 AndroidFuture<IBackupManagerMonitor> resultFuture = mTransportFutures.newFuture(); 373 mTransportBinder.getBackupManagerMonitor(resultFuture); 374 return IBackupManagerMonitor.Stub.asInterface((IBinder) getFutureResult(resultFuture)); 375 } 376 377 /** 378 * Allows the {@link TransportConnection} to notify this client 379 * if the underlying transport has become unusable. If that happens 380 * we want to cancel all active futures or callbacks. 381 */ onBecomingUnusable()382 void onBecomingUnusable() { 383 mCallbackPool.cancelActiveCallbacks(); 384 mTransportFutures.cancelActiveFutures(); 385 } 386 getFutureResult(AndroidFuture<T> future)387 private <T> T getFutureResult(AndroidFuture<T> future) { 388 try { 389 return future.get(BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis(), 390 TimeUnit.MILLISECONDS); 391 } catch (InterruptedException | ExecutionException | TimeoutException 392 | CancellationException e) { 393 Slog.w(TAG, "Failed to get result from transport:", e); 394 return null; 395 } finally { 396 mTransportFutures.remove(future); 397 } 398 } 399 400 private static class TransportFutures { 401 private final Object mActiveFuturesLock = new Object(); 402 private final Set<AndroidFuture<?>> mActiveFutures = new HashSet<>(); 403 newFuture()404 <T> AndroidFuture<T> newFuture() { 405 AndroidFuture<T> future = new AndroidFuture<>(); 406 synchronized (mActiveFuturesLock) { 407 mActiveFutures.add(future); 408 } 409 return future; 410 } 411 remove(AndroidFuture<T> future)412 <T> void remove(AndroidFuture<T> future) { 413 synchronized (mActiveFuturesLock) { 414 mActiveFutures.remove(future); 415 } 416 } 417 cancelActiveFutures()418 void cancelActiveFutures() { 419 synchronized (mActiveFuturesLock) { 420 for (AndroidFuture<?> future : mActiveFutures) { 421 try { 422 future.cancel(true); 423 } catch (CancellationException ignored) { 424 // This is expected, so ignore the exception. 425 } 426 } 427 mActiveFutures.clear(); 428 } 429 } 430 } 431 432 private static class TransportStatusCallbackPool { 433 private static final int MAX_POOL_SIZE = 100; 434 435 private final Object mPoolLock = new Object(); 436 private final Queue<TransportStatusCallback> mCallbackPool = new ArrayDeque<>(); 437 private final Set<TransportStatusCallback> mActiveCallbacks = new HashSet<>(); 438 acquire()439 TransportStatusCallback acquire() { 440 synchronized (mPoolLock) { 441 TransportStatusCallback callback = mCallbackPool.poll(); 442 if (callback == null) { 443 callback = new TransportStatusCallback(); 444 } 445 callback.reset(); 446 mActiveCallbacks.add(callback); 447 return callback; 448 } 449 } 450 recycle(TransportStatusCallback callback)451 void recycle(TransportStatusCallback callback) { 452 synchronized (mPoolLock) { 453 mActiveCallbacks.remove(callback); 454 if (mCallbackPool.size() > MAX_POOL_SIZE) { 455 Slog.d(TAG, "TransportStatusCallback pool size exceeded"); 456 return; 457 } 458 mCallbackPool.add(callback); 459 } 460 } 461 cancelActiveCallbacks()462 void cancelActiveCallbacks() { 463 synchronized (mPoolLock) { 464 for (TransportStatusCallback callback : mActiveCallbacks) { 465 try { 466 callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); 467 // This waits for status to propagate before the callback is reset. 468 callback.getOperationStatus(); 469 } catch (RemoteException ex) { 470 // Nothing we can do. 471 } 472 if (mCallbackPool.size() < MAX_POOL_SIZE) { 473 mCallbackPool.add(callback); 474 } 475 } 476 mActiveCallbacks.clear(); 477 } 478 } 479 } 480 } 481