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