• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.ondevicepersonalization.services.download.mdd;
18 
19 import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
20 
21 import android.content.Context;
22 import android.content.SharedPreferences;
23 import android.net.Uri;
24 import android.util.Log;
25 
26 import androidx.annotation.NonNull;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
30 
31 import com.google.android.downloader.AndroidDownloaderLogger;
32 import com.google.android.downloader.ConnectivityHandler;
33 import com.google.android.downloader.DownloadConstraints;
34 import com.google.android.downloader.Downloader;
35 import com.google.android.downloader.PlatformUrlEngine;
36 import com.google.android.downloader.UrlEngine;
37 import com.google.android.libraries.mobiledatadownload.DownloadException;
38 import com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest;
39 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
40 import com.google.android.libraries.mobiledatadownload.downloader.offroad.ExceptionHandler;
41 import com.google.android.libraries.mobiledatadownload.downloader.offroad.Offroad2FileDownloader;
42 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
43 import com.google.android.libraries.mobiledatadownload.file.integration.downloader.DownloadMetadataStore;
44 import com.google.android.libraries.mobiledatadownload.file.integration.downloader.SharedPreferencesDownloadMetadata;
45 import com.google.common.base.Optional;
46 import com.google.common.util.concurrent.Futures;
47 import com.google.common.util.concurrent.ListenableFuture;
48 
49 import java.util.concurrent.Executor;
50 
51 /**
52  * A OnDevicePersonalization custom {@link FileDownloader}
53  */
54 public class OnDevicePersonalizationFileDownloader implements FileDownloader {
55     private static final String TAG = "OnDevicePersonalizationFileDownloader";
56 
57     /** Downloader Connection Timeout in Milliseconds. */
58     private static final int DOWNLOADER_CONNECTION_TIMEOUT_MS = 10 * 1000; // 10 seconds
59     /** Downloader Read Timeout in Milliseconds. */
60     private static final int DOWNLOADER_READ_TIMEOUT_MS = 10 * 1000; // 10 seconds.
61     /** Downloader max download threads. */
62     private static final int DOWNLOADER_MAX_DOWNLOAD_THREADS = 2;
63 
64     private static final String MDD_METADATA_SHARED_PREFERENCES = "mdd_metadata_store";
65 
66     private final SynchronousFileStorage mFileStorage;
67     private final Context mContext;
68 
69     private final Executor mDownloadExecutor;
70 
71     private final FileDownloader mOffroad2FileDownloader;
72     private final FileDownloader mLocalFileDownloader;
73 
OnDevicePersonalizationFileDownloader( SynchronousFileStorage fileStorage, Executor downloadExecutor, Context context)74     public OnDevicePersonalizationFileDownloader(
75             SynchronousFileStorage fileStorage, Executor downloadExecutor,
76             Context context) {
77         this.mFileStorage = fileStorage;
78         this.mDownloadExecutor = downloadExecutor;
79         this.mContext = context;
80 
81         this.mOffroad2FileDownloader = getOffroad2FileDownloader(mContext, mFileStorage,
82                 mDownloadExecutor);
83         this.mLocalFileDownloader = new OnDevicePersonalizationLocalFileDownloader(mFileStorage,
84                 mDownloadExecutor, mContext);
85 
86     }
87 
88     @NonNull
getOffroad2FileDownloader( @onNull Context context, @NonNull SynchronousFileStorage fileStorage, @NonNull Executor downloadExecutor)89     private static FileDownloader getOffroad2FileDownloader(
90             @NonNull Context context, @NonNull SynchronousFileStorage fileStorage,
91             @NonNull Executor downloadExecutor) {
92         DownloadMetadataStore downloadMetadataStore = getDownloadMetadataStore(context);
93 
94         Downloader downloader =
95                 new Downloader.Builder()
96                         .withIOExecutor(OnDevicePersonalizationExecutors.getBlockingExecutor())
97                         .withConnectivityHandler(new NoOpConnectivityHandler())
98                         .withMaxConcurrentDownloads(DOWNLOADER_MAX_DOWNLOAD_THREADS)
99                         .withLogger(new AndroidDownloaderLogger())
100                         .addUrlEngine("https", getUrlEngine())
101                         .build();
102 
103         return new Offroad2FileDownloader(
104                 downloader,
105                 fileStorage,
106                 downloadExecutor,
107                 /* authTokenProvider */ null,
108                 downloadMetadataStore,
109                 getExceptionHandler(),
110                 Optional.absent());
111     }
112 
113     @NonNull
getExceptionHandler()114     private static ExceptionHandler getExceptionHandler() {
115         return ExceptionHandler.withDefaultHandling();
116     }
117 
118     @NonNull
getDownloadMetadataStore(@onNull Context context)119     private static DownloadMetadataStore getDownloadMetadataStore(@NonNull Context context) {
120         SharedPreferences sharedPrefs =
121                 context.getSharedPreferences(MDD_METADATA_SHARED_PREFERENCES, Context.MODE_PRIVATE);
122         DownloadMetadataStore downloadMetadataStore =
123                 new SharedPreferencesDownloadMetadata(
124                         sharedPrefs, OnDevicePersonalizationExecutors.getBackgroundExecutor());
125         return downloadMetadataStore;
126     }
127 
128     @NonNull
getUrlEngine()129     private static UrlEngine getUrlEngine() {
130         // TODO(b/219594618): Switch to use CronetUrlEngine.
131         return new PlatformUrlEngine(
132                 OnDevicePersonalizationExecutors.getBlockingExecutor(),
133                 DOWNLOADER_CONNECTION_TIMEOUT_MS,
134                 DOWNLOADER_READ_TIMEOUT_MS);
135     }
136 
137     @Override
startDownloading(DownloadRequest downloadRequest)138     public ListenableFuture<Void> startDownloading(DownloadRequest downloadRequest) {
139         Uri fileUri = downloadRequest.fileUri();
140         String urlToDownload = downloadRequest.urlToDownload();
141         Log.d(TAG, "startDownloading; fileUri: " + fileUri + "; urlToDownload: " + urlToDownload);
142 
143         Uri uriToDownload = Uri.parse(urlToDownload);
144         if (uriToDownload == null || fileUri == null) {
145             Log.e(TAG, ": Invalid urlToDownload " + urlToDownload);
146             return immediateFailedFuture(new IllegalArgumentException("Invalid urlToDownload"));
147         }
148 
149         // Check for debug enabled package and download url.
150         if (OnDevicePersonalizationLocalFileDownloader.isLocalOdpUri(uriToDownload)) {
151             Log.d(TAG, "Handling debug download url: " + urlToDownload);
152             return mLocalFileDownloader.startDownloading(downloadRequest);
153         }
154 
155         if (!urlToDownload.startsWith("https")) {
156             Log.e(TAG, "File url is not secure: " + urlToDownload);
157             return immediateFailedFuture(
158                     DownloadException.builder()
159                             .setDownloadResultCode(
160                                     DownloadException.DownloadResultCode.INSECURE_URL_ERROR)
161                             .build());
162         }
163 
164         return mOffroad2FileDownloader.startDownloading(downloadRequest);
165     }
166 
167     // Connectivity constraints will be checked by JobScheduler/WorkManager instead.
168     @VisibleForTesting
169     static class NoOpConnectivityHandler implements ConnectivityHandler {
170         @Override
checkConnectivity(DownloadConstraints constraints)171         public ListenableFuture<Void> checkConnectivity(DownloadConstraints constraints) {
172             return Futures.immediateVoidFuture();
173         }
174     }
175 }
176