• 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.content.Context;
26 import android.content.res.Resources;
27 import android.content.res.loader.ResourcesLoader;
28 import android.content.res.loader.ResourcesProvider;
29 import android.net.Uri;
30 import android.os.Build;
31 import android.os.ParcelFileDescriptor;
32 import android.util.ArrayMap;
33 
34 import androidx.annotation.NonNull;
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.io.IOException;
50 import java.util.Map;
51 import java.util.concurrent.ExecutionException;
52 
53 /**
54  * Manages OTA (over the air) Resources downloaded from MDD. This allows device to use updated OTA
55  * resources. Currently only strings are supported.
56  */
57 // TODO(b/269798827): Enable for R.
58 @RequiresApi(Build.VERSION_CODES.S)
59 public class OTAResourcesManager {
60     // this value needs to be updated if bundled resources are updated
61     private static final long BUNDLED_RESOURCES_VERSION = 0;
62     private static final long NO_OTA_RESOURCES_VERSION = -1;
63     private static final String FILE_GROUP_NAME = "ui-ota-strings";
64     private static final String DOWNLOADED_OTA_FILE_ID = "resources.arsc";
65     private static final ResourcesLoader OTAResourcesLoader = new ResourcesLoader();
66 
67     private static long sOTAResourcesVersion = NO_OTA_RESOURCES_VERSION;
68 
69     /**
70      * If shouldRefresh, then create a new OTA {@link ResourcesLoader} from ARSC file on device.
71      * Checks if OTA version > bundled version: If true, then add OTAResourcesLoader to the current
72      * context's {@link Resources}. Else, do nothing.
73      *
74      * @param context {@link Context}
75      */
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 
refreshOTAResources(Context context)85     static void refreshOTAResources(Context context) {
86         LogUtil.d("createResourceLoaderFromMDDFiles called.");
87         Map<String, ClientFile> downloadedOTAFiles = getDownloadedFiles(context);
88 
89         // check if there are OTA Resources
90         if (downloadedOTAFiles == null || downloadedOTAFiles.size() <= 0) {
91             return;
92         }
93         // get OTA strings file
94         ClientFile resourcesFile = downloadedOTAFiles.get(DOWNLOADED_OTA_FILE_ID);
95         if (resourcesFile == null) {
96             LogUtil.d("No OTA file");
97             return;
98         }
99         if (!resourcesFile.hasFileUri()) {
100             ErrorLogUtil.e(
101                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__DOWNLOADED_OTA_FILE_ERROR,
102                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX,
103                     OTAResourcesManager.class.getSimpleName(),
104                     new Object() {}.getClass().getEnclosingMethod().getName());
105             return;
106         }
107         File f = new File(context.getDataDir() + Uri.parse(resourcesFile.getFileUri()).getPath());
108         LogUtil.d("got this file:" + resourcesFile.getFileUri());
109         // Clear previous ResourceProvider and add new one created from arsc file
110         OTAResourcesLoader.clearProviders();
111         try {
112             ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, MODE_READ_ONLY);
113             OTAResourcesLoader.addProvider(ResourcesProvider.loadFromTable(fd, null));
114             fd.close();
115         } catch (IOException e) {
116             LogUtil.e("Exception while trying to add ResourcesProvider:" + e);
117             ErrorLogUtil.e(
118                     e,
119                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__RESOURCES_PROVIDER_ADD_ERROR,
120                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
121             OTAResourcesLoader.clearProviders();
122         } catch (Exception e) {
123             LogUtil.e("Caught exception while adding providers: " + e.getMessage());
124             ErrorLogUtil.e(
125                     e,
126                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__RESOURCES_PROVIDER_ADD_ERROR,
127                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
128             OTAResourcesLoader.clearProviders();
129         }
130     }
131 
132     /**
133      * This function populates metadata files to a map.
134      *
135      * @param context {@link Context}
136      * @return A {@link Map} containing downloaded fileId mapped to ClientFile or null if no
137      *     downloaded files found.
138      */
getDownloadedFiles(@onNull Context context)139     static @Nullable Map<String, ClientFile> getDownloadedFiles(@NonNull Context context) {
140         LogUtil.d("getDownloadedFiles called.");
141         MobileDataDownload mobileDataDownload =
142                 MobileDataDownloadFactory.getMdd(context, FlagsFactory.getFlags());
143         GetFileGroupRequest getFileGroupRequest =
144                 GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build();
145         ClientFileGroup fileGroup;
146         try {
147             // TODO(b/242908564):We potentially cannot do callback here since we need to get the OTA
148             //  strings before we create the UI, as the UI needs the updated strings if they exist.
149             fileGroup = mobileDataDownload.getFileGroup(getFileGroupRequest).get();
150         } catch (ExecutionException | InterruptedException e) {
151             LogUtil.e(e, "Unable to load MDD file group for " + FILE_GROUP_NAME);
152             ErrorLogUtil.e(
153                     e,
154                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__LOAD_MDD_FILE_GROUP_FAILURE,
155                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
156             return null;
157         }
158 
159         if (fileGroup == null || fileGroup.getStatus() != ClientFileGroup.Status.DOWNLOADED) {
160             return null;
161         }
162         LogUtil.d("found fileGroup: " + fileGroup);
163         Map<String, ClientFile> downloadedFiles = new ArrayMap<>();
164         if (fileGroup != null) {
165             LogUtil.d("Populating downloadFiles map for " + FILE_GROUP_NAME);
166             for (ClientFile file : fileGroup.getFileList()) {
167                 downloadedFiles.put(file.getFileId(), file);
168             }
169             LogUtil.d("setting fileGroup version for " + FILE_GROUP_NAME);
170             sOTAResourcesVersion = fileGroup.getBuildId();
171         }
172         return downloadedFiles;
173     }
174 }
175