• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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