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