• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.adservices.download;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.net.Uri;
22 import android.os.Build;
23 import android.util.Pair;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.RequiresApi;
27 
28 import com.android.adservices.LogUtil;
29 import com.android.adservices.data.enrollment.EnrollmentDao;
30 import com.android.adservices.service.Flags;
31 import com.android.adservices.service.FlagsFactory;
32 import com.android.adservices.service.enrollment.EnrollmentData;
33 
34 import com.google.android.libraries.mobiledatadownload.GetFileGroupRequest;
35 import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
36 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
37 import com.google.android.libraries.mobiledatadownload.file.openers.ReadStreamOpener;
38 import com.google.common.annotations.VisibleForTesting;
39 import com.google.common.util.concurrent.Futures;
40 import com.google.common.util.concurrent.ListenableFuture;
41 import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
42 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup;
43 
44 import java.io.BufferedReader;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.InputStreamReader;
48 import java.util.Arrays;
49 import java.util.concurrent.ExecutionException;
50 
51 /** Handles EnrollmentData download from MDD server to device. */
52 // TODO(b/269798827): Enable for R.
53 @RequiresApi(Build.VERSION_CODES.S)
54 public class EnrollmentDataDownloadManager {
55     private final Context mContext;
56     private static volatile EnrollmentDataDownloadManager sEnrollmentDataDownloadManager;
57     private final MobileDataDownload mMobileDataDownload;
58     private final SynchronousFileStorage mFileStorage;
59 
60     private static final String GROUP_NAME = "adtech_enrollment_data";
61     private static final String DOWNLOADED_ENROLLMENT_DATA_FILE_ID = "adtech_enrollment_data.csv";
62     private static final String ENROLLMENT_FILE_READ_STATUS_SHARED_PREFERENCES =
63             "enrollment_data_read_status";
64 
65     @VisibleForTesting
EnrollmentDataDownloadManager(Context context, Flags flags)66     EnrollmentDataDownloadManager(Context context, Flags flags) {
67         mContext = context.getApplicationContext();
68         mMobileDataDownload = MobileDataDownloadFactory.getMdd(context, flags);
69         mFileStorage = MobileDataDownloadFactory.getFileStorage(context);
70     }
71 
72     /** Gets an instance of EnrollmentDataDownloadManager to be used. */
getInstance(@onNull Context context)73     public static EnrollmentDataDownloadManager getInstance(@NonNull Context context) {
74         if (sEnrollmentDataDownloadManager == null) {
75             synchronized (EnrollmentDataDownloadManager.class) {
76                 if (sEnrollmentDataDownloadManager == null) {
77                     sEnrollmentDataDownloadManager =
78                             new EnrollmentDataDownloadManager(context, FlagsFactory.getFlags());
79                 }
80             }
81         }
82         return sEnrollmentDataDownloadManager;
83     }
84 
85     /**
86      * Find, open and read the enrollment data file from MDD and only insert new data into the
87      * enrollment database.
88      */
readAndInsertEnrolmentDataFromMdd()89     public ListenableFuture<DownloadStatus> readAndInsertEnrolmentDataFromMdd() {
90         LogUtil.d("Reading MDD data from file.");
91         Pair<ClientFile, String> FileGroupAndBuildIdPair = getEnrollmentDataFile();
92         if (FileGroupAndBuildIdPair == null || FileGroupAndBuildIdPair.first == null) {
93             return Futures.immediateFuture(DownloadStatus.NO_FILE_AVAILABLE);
94         }
95 
96         ClientFile enrollmentDataFile = FileGroupAndBuildIdPair.first;
97         String fileGroupBuildId = FileGroupAndBuildIdPair.second;
98         SharedPreferences sharedPrefs =
99                 mContext.getSharedPreferences(
100                         ENROLLMENT_FILE_READ_STATUS_SHARED_PREFERENCES, Context.MODE_PRIVATE);
101         if (sharedPrefs.getBoolean(fileGroupBuildId, false)) {
102             LogUtil.d(
103                     "Enrollment data build id = %s has been saved into DB. Skip adding same data.",
104                     fileGroupBuildId);
105             return Futures.immediateFuture(DownloadStatus.SKIP);
106         }
107 
108         if (readDownloadedFile(enrollmentDataFile)) {
109             SharedPreferences.Editor editor = sharedPrefs.edit();
110             editor.clear().putBoolean(fileGroupBuildId, true);
111             if (!editor.commit()) {
112                 // TODO(b/280579966): Add logging using CEL.
113                 LogUtil.e("Saving to the enrollment file read status sharedpreference failed");
114             }
115             LogUtil.d("Inserted new enrollment data build id = %s into DB.", fileGroupBuildId);
116             return Futures.immediateFuture(DownloadStatus.SUCCESS);
117         } else {
118             return Futures.immediateFuture(DownloadStatus.PARSING_FAILED);
119         }
120     }
121 
readDownloadedFile(ClientFile enrollmentDataFile)122     private boolean readDownloadedFile(ClientFile enrollmentDataFile) {
123         LogUtil.d("Inserting MDD data into DB.");
124         try {
125             InputStream inputStream =
126                     mFileStorage.open(
127                             Uri.parse(enrollmentDataFile.getFileUri()), ReadStreamOpener.create());
128             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
129             bufferedReader.readLine();
130             String line = null;
131             // While loop runs from the second line.
132             EnrollmentDao enrollmentDao = EnrollmentDao.getInstance(mContext);
133             while ((line = bufferedReader.readLine()) != null) {
134                 // Constructs EnrollmentData object and save it into DB.
135                 String[] data = line.split(",");
136                 if (data.length == 8) {
137                     String enrollmentId = data[0];
138                     LogUtil.d("Adding enrollmentId - %s", enrollmentId);
139                     EnrollmentData enrollmentData =
140                             new EnrollmentData.Builder()
141                                     .setEnrollmentId(enrollmentId)
142                                     .setCompanyId(data[1])
143                                     .setSdkNames(data[2])
144                                     .setAttributionSourceRegistrationUrl(
145                                             data[3].contains(" ")
146                                                     ? Arrays.asList(data[3].split(" "))
147                                                     : Arrays.asList(data[3]))
148                                     .setAttributionTriggerRegistrationUrl(
149                                             data[4].contains(" ")
150                                                     ? Arrays.asList(data[4].split(" "))
151                                                     : Arrays.asList(data[4]))
152                                     .setAttributionReportingUrl(
153                                             data[5].contains(" ")
154                                                     ? Arrays.asList(data[5].split(" "))
155                                                     : Arrays.asList(data[5]))
156                                     .setRemarketingResponseBasedRegistrationUrl(
157                                             data[6].contains(" ")
158                                                     ? Arrays.asList(data[6].split(" "))
159                                                     : Arrays.asList(data[6]))
160                                     .setEncryptionKeyUrl(
161                                             data[7].contains(" ")
162                                                     ? Arrays.asList(data[7].split(" "))
163                                                     : Arrays.asList(data[7]))
164                                     .build();
165                     enrollmentDao.insert(enrollmentData);
166                 }
167             }
168             return true;
169         } catch (IOException e) {
170             return false;
171         }
172     }
173 
174     @VisibleForTesting
175     public enum DownloadStatus {
176         SUCCESS,
177         NO_FILE_AVAILABLE,
178         PARSING_FAILED,
179         // Skip reading and inserting same enrollment data to DB if the data has been saved
180         // previously.
181         SKIP;
182     }
183 
getEnrollmentDataFile()184     private Pair<ClientFile, String> getEnrollmentDataFile() {
185         GetFileGroupRequest getFileGroupRequest =
186                 GetFileGroupRequest.newBuilder().setGroupName(GROUP_NAME).build();
187         try {
188             ListenableFuture<ClientFileGroup> fileGroupFuture =
189                     mMobileDataDownload.getFileGroup(getFileGroupRequest);
190             ClientFileGroup fileGroup = fileGroupFuture.get();
191             if (fileGroup == null) {
192                 LogUtil.d("MDD has not downloaded the Enrollment Data Files yet.");
193                 return null;
194             }
195             String fileGroupBuildId = String.valueOf(fileGroup.getBuildId());
196             ClientFile enrollmentDataFile = null;
197             for (ClientFile file : fileGroup.getFileList()) {
198                 if (file.getFileId().equals(DOWNLOADED_ENROLLMENT_DATA_FILE_ID)) {
199                     enrollmentDataFile = file;
200                 }
201             }
202             return Pair.create(enrollmentDataFile, fileGroupBuildId);
203 
204         } catch (ExecutionException | InterruptedException e) {
205             LogUtil.e(e, "Unable to load MDD file group.");
206             return null;
207         }
208     }
209 }
210