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.ondevicepersonalization.services.download.mdd;
18 
19 import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
20 import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
21 
22 import android.content.Context;
23 import android.net.Uri;
24 
25 
26 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
27 
28 import com.google.android.libraries.mobiledatadownload.DownloadException;
29 import com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest;
30 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
31 import com.google.android.libraries.mobiledatadownload.file.Opener;
32 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
33 import com.google.android.libraries.mobiledatadownload.file.openers.WriteStreamOpener;
34 import com.google.common.io.ByteStreams;
35 import com.google.common.util.concurrent.Futures;
36 import com.google.common.util.concurrent.ListenableFuture;
37 
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.util.Set;
41 import java.util.concurrent.Executor;
42 
43 /**
44  * A {@link FileDownloader} that "downloads" by copying the file from the Resources.
45  * Files for local download should be placed in the package's Resources.
46  *
47  * <p>Note that OnDevicePersonalizationLocalFileDownloader ignores DownloadConditions.
48  */
49 public final class OnDevicePersonalizationLocalFileDownloader implements FileDownloader {
50 
51     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
52     private static final String TAG = "OnDevicePersonalizationLocalFileDownloader";
53 
54     /**
55      * The uri to download should be formatted as an android.resource uri:
56      * android.resource://<package_name>/<resource_type>/<resource_name>
57      */
58     private static final Set<String> sDebugSchemes = Set.of("android.resource", "file");
59 
60     private final Executor mExecutor;
61     private final SynchronousFileStorage mFileStorage;
62     private final Context mContext;
63 
OnDevicePersonalizationLocalFileDownloader( SynchronousFileStorage fileStorage, Executor executor, Context context)64     public OnDevicePersonalizationLocalFileDownloader(
65             SynchronousFileStorage fileStorage, Executor executor,
66             Context context) {
67         this.mFileStorage = fileStorage;
68         this.mExecutor = executor;
69         this.mContext = context;
70     }
71 
72     /**
73      * Determines if given uri is local odp uri
74      *
75      * @return true if uri is a local odp uri, false otherwise
76      */
isLocalOdpUri(Uri uri)77     public static boolean isLocalOdpUri(Uri uri) {
78         String scheme = uri.getScheme();
79         if (scheme != null && sDebugSchemes.contains(scheme)) {
80             return true;
81         }
82         return false;
83     }
84 
85     /**
86      * Performs a localFile download for the given request
87      */
88     @Override
startDownloading(DownloadRequest downloadRequest)89     public ListenableFuture<Void> startDownloading(DownloadRequest downloadRequest) {
90         return Futures.submitAsync(() -> startDownloadingInternal(downloadRequest),
91                 mExecutor);
92     }
93 
startDownloadingInternal(DownloadRequest downloadRequest)94     private ListenableFuture<Void> startDownloadingInternal(DownloadRequest downloadRequest) {
95         Uri fileUri = downloadRequest.fileUri();
96         String urlToDownload = downloadRequest.urlToDownload();
97         Uri uriToDownload = Uri.parse(urlToDownload);
98         // Strip away the query params for local download.
99         uriToDownload = new Uri.Builder()
100                 .scheme(uriToDownload.getScheme())
101                 .authority(uriToDownload.getAuthority())
102                 .path(uriToDownload.getPath()).build();
103         sLogger.d(TAG + ": Starting local download for url: " + urlToDownload);
104 
105         try {
106             Opener<OutputStream> writeStreamOpener = WriteStreamOpener.create();
107             long writtenBytes;
108             try (OutputStream out = mFileStorage.open(fileUri, writeStreamOpener)) {
109                 InputStream in = mContext.getContentResolver().openInputStream(uriToDownload);
110                 writtenBytes = ByteStreams.copy(in, out);
111             }
112             sLogger.d(TAG + ": File URI " + fileUri
113                     + " download complete, writtenBytes: " + writtenBytes);
114         } catch (Exception e) {
115             sLogger.e(e, TAG + ": %s: startDownloading got exception", urlToDownload);
116             return immediateFailedFuture(
117                     DownloadException.builder()
118                             .setDownloadResultCode(
119                                     DownloadException.DownloadResultCode
120                                             .ANDROID_DOWNLOADER_HTTP_ERROR)
121                             .build());
122         }
123 
124         return immediateVoidFuture();
125     }
126 }
127