• 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 package com.android.adservices.ui;
17 
18 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
19 
20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DOWNLOADED_OTA_FILE_ERROR;
21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__LOAD_MDD_FILE_GROUP_FAILURE;
22 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__RESOURCES_PROVIDER_ADD_ERROR;
23 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX;
24 
25 import android.annotation.SuppressLint;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.content.res.loader.ResourcesLoader;
29 import android.content.res.loader.ResourcesProvider;
30 import android.net.Uri;
31 import android.os.Build;
32 import android.os.ParcelFileDescriptor;
33 import android.util.ArrayMap;
34 
35 import androidx.annotation.Nullable;
36 import androidx.annotation.RequiresApi;
37 
38 import com.android.adservices.LogUtil;
39 import com.android.adservices.download.MobileDataDownloadFactory;
40 import com.android.adservices.errorlogging.ErrorLogUtil;
41 import com.android.adservices.service.FlagsFactory;
42 
43 import com.google.android.libraries.mobiledatadownload.GetFileGroupRequest;
44 import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
45 import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
46 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup;
47 
48 import java.io.File;
49 import java.util.Map;
50 import java.util.concurrent.ExecutionException;
51 
52 /**
53  * Manages OTA (over the air) Resources downloaded from MDD. This allows device to use updated OTA
54  * resources. Currently only strings are supported.
55  */
56 @RequiresApi(Build.VERSION_CODES.S)
57 public class OTAResourcesManager {
58     // this value needs to be updated if bundled resources are updated
59     private static final long BUNDLED_RESOURCES_VERSION = 4265;
60     private static final long NO_OTA_RESOURCES_VERSION = -1;
61     private static final String FILE_GROUP_NAME = "ui-ota-strings";
62     public static final String DOWNLOADED_OTA_FILE_ID = "resources.arsc";
63     public static final String DOWNLOADED_OTA_APK_ID = "AdServicesOtaResourcesApp.apk";
64     private static final ResourcesLoader OTAResourcesLoader = new ResourcesLoader();
65 
66     private static long sOTAResourcesVersion = NO_OTA_RESOURCES_VERSION;
67 
68     /**
69      * If shouldRefresh, then create a new OTA {@link ResourcesLoader} from ARSC file on device.
70      * Checks if OTA version > bundled version: If true, then add OTAResourcesLoader to the current
71      * context's {@link Resources}. Else, do nothing.
72      *
73      * @param context {@link Context}
74      */
75     @SuppressWarnings("AvoidStaticContext") // UX class
applyOTAResources(Context context, boolean shouldRefresh)76     public static void applyOTAResources(Context context, boolean shouldRefresh) {
77         if (shouldRefresh || sOTAResourcesVersion == NO_OTA_RESOURCES_VERSION) {
78             refreshOTAResources(context.getApplicationContext());
79         }
80         if (sOTAResourcesVersion > BUNDLED_RESOURCES_VERSION) {
81             context.getApplicationContext().getResources().addLoaders(OTAResourcesLoader);
82         }
83     }
84 
85     @SuppressWarnings("AvoidStaticContext") // UX class
refreshOTAResources(Context context)86     static void refreshOTAResources(Context context) {
87         LogUtil.d("createResourceLoaderFromMDDFiles called.");
88         Map<String, ClientFile> downloadedOTAFiles = getDownloadedFiles();
89 
90         // check if there are OTA Resources
91         if (downloadedOTAFiles == null || downloadedOTAFiles.size() == 0) {
92             return;
93         }
94         // get OTA strings file
95         File resourcesFile = getOtaFile(context, downloadedOTAFiles, DOWNLOADED_OTA_FILE_ID);
96         // get OTA resources apk
97         File resourcesApk = getOtaFile(context, downloadedOTAFiles, DOWNLOADED_OTA_APK_ID);
98         if (resourcesFile == null && resourcesApk == null) {
99             LogUtil.d("No OTA files");
100             return;
101         }
102 
103         // Clear previous ResourceProvider
104         OTAResourcesLoader.clearProviders();
105         // Add new ResourceProvider created from arsc file
106         if (resourcesFile != null) {
107             try {
108                 ParcelFileDescriptor fd = ParcelFileDescriptor.open(resourcesFile, MODE_READ_ONLY);
109                 OTAResourcesLoader.addProvider(ResourcesProvider.loadFromTable(fd, null));
110                 fd.close();
111             } catch (Exception e) {
112                 LogUtil.e("Caught exception while adding OTA string provider: " + e.getMessage());
113                 ErrorLogUtil.e(
114                         e,
115                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__RESOURCES_PROVIDER_ADD_ERROR,
116                         AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
117                 OTAResourcesLoader.clearProviders();
118             }
119         }
120         // Add new ResourceProvider created from OTA apk
121         else if (resourcesApk != null) {
122             try {
123                 ParcelFileDescriptor fd = ParcelFileDescriptor.open(resourcesApk, MODE_READ_ONLY);
124                 OTAResourcesLoader.addProvider(ResourcesProvider.loadFromApk(fd, null));
125                 fd.close();
126             } catch (Exception e) {
127                 LogUtil.e("Caught exception while adding OTA apk provider: " + e.getMessage());
128                 ErrorLogUtil.e(
129                         e,
130                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__RESOURCES_PROVIDER_ADD_ERROR,
131                         AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
132             }
133         }
134     }
135 
136     @SuppressWarnings("AvoidStaticContext") // UX class
getOtaFile( Context context, Map<String, ClientFile> otaFilesMap, String fileId)137     private static File getOtaFile(
138             Context context, Map<String, ClientFile> otaFilesMap, String fileId) {
139         // get OTA file
140         ClientFile otaFile = otaFilesMap.get(fileId);
141         if (otaFile == null) {
142             LogUtil.d(fileId + " not found");
143             return null;
144         }
145         if (!otaFile.hasFileUri()) {
146             ErrorLogUtil.e(
147                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DOWNLOADED_OTA_FILE_ERROR,
148                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
149             return null;
150         }
151         @SuppressLint("NewAdServicesFile")
152         File f = new File(context.getDataDir() + Uri.parse(otaFile.getFileUri()).getPath());
153         LogUtil.d("got this file:" + otaFile.getFileUri());
154         return f;
155     }
156 
157     /**
158      * This function populates metadata files to a map.
159      *
160      * @return A {@link Map} containing downloaded fileId mapped to ClientFile or null if no
161      *     downloaded files found.
162      */
getDownloadedFiles()163     static @Nullable Map<String, ClientFile> getDownloadedFiles() {
164         LogUtil.d("getDownloadedFiles called.");
165         MobileDataDownload mobileDataDownload =
166                 MobileDataDownloadFactory.getMdd(FlagsFactory.getFlags());
167         GetFileGroupRequest getFileGroupRequest =
168                 GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build();
169         ClientFileGroup fileGroup;
170         try {
171             // TODO(b/242908564):We potentially cannot do callback here since we need to get the OTA
172             //  strings before we create the UI, as the UI needs the updated strings if they exist.
173             fileGroup = mobileDataDownload.getFileGroup(getFileGroupRequest).get();
174         } catch (ExecutionException | InterruptedException e) {
175             LogUtil.e(e, "Unable to load MDD file group for " + FILE_GROUP_NAME);
176             ErrorLogUtil.e(
177                     e,
178                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__LOAD_MDD_FILE_GROUP_FAILURE,
179                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
180             return null;
181         }
182 
183         if (fileGroup == null || fileGroup.getStatus() != ClientFileGroup.Status.DOWNLOADED) {
184             return null;
185         }
186         LogUtil.d("found fileGroup: " + fileGroup);
187         Map<String, ClientFile> downloadedFiles = new ArrayMap<>();
188         if (fileGroup != null) {
189             LogUtil.d("Populating downloadFiles map for " + FILE_GROUP_NAME);
190             for (ClientFile file : fileGroup.getFileList()) {
191                 downloadedFiles.put(file.getFileId(), file);
192             }
193             LogUtil.d("setting fileGroup version for " + FILE_GROUP_NAME);
194             sOTAResourcesVersion = fileGroup.getBuildId();
195         }
196         return downloadedFiles;
197     }
198 }
199