• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.textclassifier.downloader;
18 
19 import static com.android.textclassifier.downloader.ModelDownloadException.DEFAULT_DOWNLOADER_LIB_ERROR_CODE;
20 import static com.google.common.base.Predicates.instanceOf;
21 import static com.google.common.base.Throwables.getCausalChain;
22 
23 import android.os.RemoteException;
24 import com.android.textclassifier.common.base.TcLog;
25 import com.google.android.downloader.AndroidDownloaderLogger;
26 import com.google.android.downloader.ConnectivityHandler;
27 import com.google.android.downloader.DownloadConstraints;
28 import com.google.android.downloader.DownloadRequest;
29 import com.google.android.downloader.DownloadResult;
30 import com.google.android.downloader.Downloader;
31 import com.google.android.downloader.PlatformUrlEngine;
32 import com.google.android.downloader.RequestException;
33 import com.google.android.downloader.SimpleFileDownloadDestination;
34 import com.google.common.annotations.VisibleForTesting;
35 import com.google.common.base.Preconditions;
36 import com.google.common.collect.ImmutableList;
37 import com.google.common.collect.Iterables;
38 import com.google.common.util.concurrent.FutureCallback;
39 import com.google.common.util.concurrent.Futures;
40 import com.google.common.util.concurrent.ListenableFuture;
41 import com.google.common.util.concurrent.ListeningExecutorService;
42 import com.google.common.util.concurrent.MoreExecutors;
43 import java.io.File;
44 import java.net.URI;
45 import java.util.concurrent.ExecutorService;
46 import javax.annotation.concurrent.ThreadSafe;
47 
48 /** IModelDownloaderService implementation with Android Downloader library. */
49 @ThreadSafe
50 final class ModelDownloaderServiceImpl extends IModelDownloaderService.Stub {
51   private static final String TAG = "ModelDownloaderServiceImpl";
52 
53   // Connectivity constraints will be checked by WorkManager instead.
54   private static class NoOpConnectivityHandler implements ConnectivityHandler {
55     @Override
checkConnectivity(DownloadConstraints constraints)56     public ListenableFuture<Void> checkConnectivity(DownloadConstraints constraints) {
57       return Futures.immediateVoidFuture();
58     }
59   }
60 
61   private final ExecutorService bgExecutorService;
62   private final Downloader downloader;
63 
ModelDownloaderServiceImpl( ExecutorService bgExecutorService, ListeningExecutorService transportExecutorService)64   public ModelDownloaderServiceImpl(
65       ExecutorService bgExecutorService, ListeningExecutorService transportExecutorService) {
66     this.bgExecutorService = bgExecutorService;
67     this.downloader =
68         new Downloader.Builder()
69             // This executor is for callbacks, not network IO. See discussions in cl/337156844
70             .withIOExecutor(bgExecutorService)
71             .withConnectivityHandler(new NoOpConnectivityHandler())
72             .addUrlEngine(
73                 // clear text traffic won't actually work without a manifest change, so http link
74                 // is still not supported on production builds.
75                 // Adding "http" here only for testing purposes.
76                 ImmutableList.of("https", "http"),
77                 new PlatformUrlEngine(
78                     // This executor handles network transportation and can stall for long
79                     transportExecutorService,
80                     /* connectTimeoutMs= */ 60 * 1000,
81                     /* readTimeoutMs= */ 60 * 1000))
82             .withLogger(new AndroidDownloaderLogger())
83             .build();
84   }
85 
86   @VisibleForTesting
ModelDownloaderServiceImpl(ExecutorService bgExecutorService, Downloader downloader)87   ModelDownloaderServiceImpl(ExecutorService bgExecutorService, Downloader downloader) {
88     this.bgExecutorService = Preconditions.checkNotNull(bgExecutorService);
89     this.downloader = Preconditions.checkNotNull(downloader);
90   }
91 
92   @Override
download(String uri, String targetFilePath, IModelDownloaderCallback callback)93   public void download(String uri, String targetFilePath, IModelDownloaderCallback callback) {
94     TcLog.d(TAG, "Download request received: " + uri);
95     try {
96       File targetFile = new File(targetFilePath);
97       File tempMetadataFile = getMetadataFile(targetFile);
98       DownloadRequest request =
99           downloader
100               .newRequestBuilder(
101                   URI.create(uri), new SimpleFileDownloadDestination(targetFile, tempMetadataFile))
102               .build();
103       downloader
104           .execute(request)
105           .transform(DownloadResult::bytesWritten, MoreExecutors.directExecutor())
106           .addCallback(
107               new FutureCallback<Long>() {
108                 @Override
109                 public void onSuccess(Long bytesWritten) {
110                   tempMetadataFile.delete();
111                   dispatchOnSuccessSafely(callback, bytesWritten);
112                 }
113 
114                 @Override
115                 public void onFailure(Throwable t) {
116                   TcLog.e(TAG, "onFailure", t);
117                   // TODO(licha): We may be able to resume the download if we keep those files
118                   targetFile.delete();
119                   tempMetadataFile.delete();
120                   // Try to infer the failure reason
121                   RequestException requestException =
122                       (RequestException)
123                           Iterables.find(
124                               getCausalChain(t),
125                               instanceOf(RequestException.class),
126                               /* defaultValue= */ null);
127                   // TODO(b/181805039): Use error code once downloader lib supports it.
128                   int downloaderLibErrorCode =
129                       requestException != null
130                           ? requestException.getErrorDetails().getHttpStatusCode()
131                           : DEFAULT_DOWNLOADER_LIB_ERROR_CODE;
132                   dispatchOnFailureSafely(callback, downloaderLibErrorCode, t);
133                 }
134               },
135               bgExecutorService);
136     } catch (Throwable t) {
137       dispatchOnFailureSafely(callback, DEFAULT_DOWNLOADER_LIB_ERROR_CODE, t);
138     }
139   }
140 
141   @VisibleForTesting
getMetadataFile(File targetFile)142   static File getMetadataFile(File targetFile) {
143     return new File(targetFile.getParentFile(), targetFile.getName() + ".metadata");
144   }
145 
dispatchOnSuccessSafely( IModelDownloaderCallback callback, long bytesWritten)146   private static void dispatchOnSuccessSafely(
147       IModelDownloaderCallback callback, long bytesWritten) {
148     try {
149       callback.onSuccess(bytesWritten);
150     } catch (RemoteException e) {
151       TcLog.e(TAG, "Unable to notify successful download", e);
152     }
153   }
154 
dispatchOnFailureSafely( IModelDownloaderCallback callback, int downloaderLibErrorCode, Throwable throwable)155   private static void dispatchOnFailureSafely(
156       IModelDownloaderCallback callback, int downloaderLibErrorCode, Throwable throwable) {
157     try {
158       callback.onFailure(downloaderLibErrorCode, throwable.getMessage());
159     } catch (RemoteException e) {
160       TcLog.e(TAG, "Unable to notify failures in download", e);
161     }
162   }
163 }
164