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; 18 19 import static com.android.ondevicepersonalization.services.FlagsConstants.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED; 20 21 import static org.junit.Assert.assertArrayEquals; 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assert.fail; 25 26 import android.adservices.ondevicepersonalization.DownloadCompletedOutputParcel; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.database.Cursor; 30 31 import androidx.test.core.app.ApplicationProvider; 32 33 import com.android.compatibility.common.util.ShellUtils; 34 import com.android.dx.mockito.inline.extended.ExtendedMockito; 35 import com.android.modules.utils.build.SdkLevel; 36 import com.android.modules.utils.testing.ExtendedMockitoRule; 37 import com.android.odp.module.common.PackageUtils; 38 import com.android.ondevicepersonalization.services.Flags; 39 import com.android.ondevicepersonalization.services.FlagsFactory; 40 import com.android.ondevicepersonalization.services.PhFlagsTestUtil; 41 import com.android.ondevicepersonalization.services.StableFlags; 42 import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper; 43 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao; 44 import com.android.ondevicepersonalization.services.data.vendor.VendorData; 45 import com.android.ondevicepersonalization.services.data.vendor.VendorDataContract; 46 import com.android.ondevicepersonalization.services.download.mdd.MobileDataDownloadFactory; 47 import com.android.ondevicepersonalization.services.download.mdd.OnDevicePersonalizationFileGroupPopulator; 48 49 import com.google.android.libraries.mobiledatadownload.DownloadFileGroupRequest; 50 import com.google.android.libraries.mobiledatadownload.MobileDataDownload; 51 import com.google.android.libraries.mobiledatadownload.RemoveFileGroupsByFilterRequest; 52 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 53 import com.google.common.util.concurrent.FutureCallback; 54 import com.google.common.util.concurrent.ListeningExecutorService; 55 import com.google.common.util.concurrent.MoreExecutors; 56 import com.google.common.util.concurrent.SettableFuture; 57 58 import org.junit.After; 59 import org.junit.Before; 60 import org.junit.Rule; 61 import org.junit.Test; 62 import org.junit.runner.RunWith; 63 import org.junit.runners.JUnit4; 64 import org.mockito.quality.Strictness; 65 66 import java.util.ArrayList; 67 import java.util.Base64; 68 import java.util.List; 69 import java.util.concurrent.CountDownLatch; 70 71 @RunWith(JUnit4.class) 72 public class OnDevicePersonalizationDataProcessingAsyncCallableTests { 73 private final Context mContext = ApplicationProvider.getApplicationContext(); 74 private OnDevicePersonalizationFileGroupPopulator mPopulator; 75 private MobileDataDownload mMdd; 76 private String mPackageName; 77 private SynchronousFileStorage mFileStorage; 78 private final VendorData mContent1 = new VendorData.Builder() 79 .setKey("key1") 80 .setData("dGVzdGRhdGEx".getBytes()) 81 .build(); 82 83 private final VendorData mContent2 = new VendorData.Builder() 84 .setKey("key2") 85 .setData("dGVzdGRhdGEy".getBytes()) 86 .build(); 87 88 private final VendorData mContentExtra = new VendorData.Builder() 89 .setKey("keyExtra") 90 .setData("extra".getBytes()) 91 .build(); 92 93 private ComponentName mService; 94 private FutureCallback mTestCallback; 95 private boolean mCallbackSuccess; 96 private boolean mCallbackFailure; 97 private CountDownLatch mLatch; 98 99 private Flags mSpyFlags = new Flags() { 100 int mIsolatedServiceDeadlineSeconds = 30; 101 @Override public int getIsolatedServiceDeadlineSeconds() { 102 return mIsolatedServiceDeadlineSeconds; 103 } 104 }; 105 106 @Rule 107 public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) 108 .mockStatic(FlagsFactory.class) 109 .spyStatic(StableFlags.class) 110 .setStrictness(Strictness.LENIENT) 111 .build(); 112 113 @Before setup()114 public void setup() throws Exception { 115 mPackageName = mContext.getPackageName(); 116 mService = ComponentName.createRelative( 117 mPackageName, "com.test.TestPersonalizationService"); 118 mFileStorage = MobileDataDownloadFactory.getFileStorage(mContext); 119 // Use direct executor to keep all work sequential for the tests 120 ListeningExecutorService executorService = MoreExecutors.newDirectExecutorService(); 121 mMdd = MobileDataDownloadFactory.getMdd(mContext, executorService, executorService); 122 mPopulator = new OnDevicePersonalizationFileGroupPopulator(mContext); 123 RemoveFileGroupsByFilterRequest request = 124 RemoveFileGroupsByFilterRequest.newBuilder().build(); 125 MobileDataDownloadFactory.getMdd(mContext).removeFileGroupsByFilter(request).get(); 126 127 // Initialize the DB as a test instance 128 OnDevicePersonalizationVendorDataDao.getInstanceForTest(mContext, mService, 129 PackageUtils.getCertDigest(mContext, mPackageName)); 130 131 PhFlagsTestUtil.setUpDeviceConfigPermissions(); 132 ExtendedMockito.doReturn(mSpyFlags).when(FlagsFactory::getFlags); 133 ExtendedMockito.doReturn(SdkLevel.isAtLeastU()).when( 134 () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED)); 135 ShellUtils.runShellCommand("settings put global hidden_api_policy 1"); 136 137 mLatch = new CountDownLatch(1); 138 mTestCallback = new FutureCallback<DownloadCompletedOutputParcel>() { 139 @Override 140 public void onSuccess(DownloadCompletedOutputParcel result) { 141 mCallbackSuccess = true; 142 mLatch.countDown(); 143 } 144 145 @Override 146 public void onFailure(Throwable t) { 147 mCallbackFailure = true; 148 mLatch.countDown(); 149 } 150 }; 151 } 152 153 @Test testRun()154 public void testRun() throws Exception { 155 OnDevicePersonalizationVendorDataDao dao = 156 OnDevicePersonalizationVendorDataDao.getInstanceForTest(mContext, mService, 157 PackageUtils.getCertDigest(mContext, mPackageName)); 158 var originalIsolatedServiceAllowList = 159 FlagsFactory.getFlags().getIsolatedServiceAllowList(); 160 PhFlagsTestUtil.setIsolatedServiceAllowList( 161 "com.android.ondevicepersonalization.servicetests"); 162 mPopulator.refreshFileGroups(mMdd).get(); 163 PhFlagsTestUtil.setIsolatedServiceAllowList(originalIsolatedServiceAllowList); 164 String fileGroupName = OnDevicePersonalizationFileGroupPopulator.createPackageFileGroupName( 165 mPackageName, mContext); 166 // Trigger the download immediately. 167 mMdd.downloadFileGroup( 168 DownloadFileGroupRequest.newBuilder().setGroupName(fileGroupName).build()).get(); 169 170 List<VendorData> existingData = new ArrayList<>(); 171 existingData.add(mContentExtra); 172 List<String> retain = new ArrayList<>(); 173 retain.add("keyExtra"); 174 assertTrue(dao.batchUpdateOrInsertVendorDataTransaction(existingData, retain, 175 100)); 176 177 OnDevicePersonalizationDataProcessingAsyncCallable callable = 178 new OnDevicePersonalizationDataProcessingAsyncCallable( 179 mPackageName, mContext, new TestInjector()); 180 181 callable.call(); 182 mLatch.await(); 183 184 Cursor cursor = dao.readAllVendorData(); 185 List<VendorData> vendorDataList = new ArrayList<>(); 186 while (cursor.moveToNext()) { 187 String key = cursor.getString( 188 cursor.getColumnIndexOrThrow(VendorDataContract.VendorDataEntry.KEY)); 189 190 byte[] data = cursor.getBlob( 191 cursor.getColumnIndexOrThrow(VendorDataContract.VendorDataEntry.DATA)); 192 193 vendorDataList.add(new VendorData.Builder() 194 .setKey(key) 195 .setData(data) 196 .build()); 197 } 198 cursor.close(); 199 assertEquals(3, vendorDataList.size()); 200 for (VendorData data : vendorDataList) { 201 if (data.getKey().equals(mContent1.getKey())) { 202 compareDataContent(mContent1, data, false); 203 } else if (data.getKey().equals(mContent2.getKey())) { 204 compareDataContent(mContent2, data, true); 205 } else if (data.getKey().equals(mContentExtra.getKey())) { 206 compareDataContent(mContentExtra, data, false); 207 } else { 208 fail("Vendor data from DB contains unexpected key"); 209 } 210 } 211 } 212 213 @Test testRunOldDataDownloaded()214 public void testRunOldDataDownloaded() throws Exception { 215 OnDevicePersonalizationVendorDataDao dao = 216 OnDevicePersonalizationVendorDataDao.getInstanceForTest(mContext, mService, 217 PackageUtils.getCertDigest(mContext, mPackageName)); 218 var originalIsolatedServiceAllowList = 219 FlagsFactory.getFlags().getIsolatedServiceAllowList(); 220 PhFlagsTestUtil.setIsolatedServiceAllowList( 221 "com.android.ondevicepersonalization.servicetests"); 222 mPopulator.refreshFileGroups(mMdd).get(); 223 PhFlagsTestUtil.setIsolatedServiceAllowList(originalIsolatedServiceAllowList); 224 String fileGroupName = OnDevicePersonalizationFileGroupPopulator.createPackageFileGroupName( 225 mPackageName, mContext); 226 // Trigger the download immediately. 227 mMdd.downloadFileGroup( 228 DownloadFileGroupRequest.newBuilder().setGroupName(fileGroupName).build()).get(); 229 230 List<VendorData> existingData = new ArrayList<>(); 231 existingData.add(mContentExtra); 232 List<String> retain = new ArrayList<>(); 233 retain.add("keyExtra"); 234 assertTrue(dao.batchUpdateOrInsertVendorDataTransaction(existingData, retain, 235 System.currentTimeMillis())); 236 237 OnDevicePersonalizationDataProcessingAsyncCallable callable = 238 new OnDevicePersonalizationDataProcessingAsyncCallable( 239 mPackageName, mContext, new TestInjector()); 240 241 callable.call(); 242 mLatch.await(); 243 244 Cursor cursor = dao.readAllVendorData(); 245 List<VendorData> vendorDataList = new ArrayList<>(); 246 while (cursor.moveToNext()) { 247 String key = cursor.getString( 248 cursor.getColumnIndexOrThrow(VendorDataContract.VendorDataEntry.KEY)); 249 250 byte[] data = cursor.getBlob( 251 cursor.getColumnIndexOrThrow(VendorDataContract.VendorDataEntry.DATA)); 252 253 vendorDataList.add(new VendorData.Builder() 254 .setKey(key) 255 .setData(data) 256 .build()); 257 } 258 cursor.close(); 259 assertEquals(1, vendorDataList.size()); 260 for (VendorData data : vendorDataList) { 261 if (data.getKey().equals(mContentExtra.getKey())) { 262 compareDataContent(mContentExtra, data, false); 263 } else { 264 fail("Vendor data from DB contains unexpected key"); 265 } 266 } 267 } 268 269 class TestInjector extends OnDevicePersonalizationDataProcessingAsyncCallable.Injector { 270 @Override getFutureCallback( SettableFuture<Boolean> settableFuture)271 FutureCallback<DownloadCompletedOutputParcel> getFutureCallback( 272 SettableFuture<Boolean> settableFuture) { 273 return mTestCallback; 274 } 275 } 276 compareDataContent(VendorData expectedData, VendorData actualData, boolean base64)277 private void compareDataContent(VendorData expectedData, VendorData actualData, 278 boolean base64) { 279 assertEquals(expectedData.getKey(), actualData.getKey()); 280 if (base64) { 281 assertArrayEquals(Base64.getDecoder().decode(expectedData.getData()), 282 actualData.getData()); 283 } else { 284 assertArrayEquals(expectedData.getData(), actualData.getData()); 285 } 286 } 287 288 @After cleanup()289 public void cleanup() { 290 OnDevicePersonalizationDbHelper dbHelper = 291 OnDevicePersonalizationDbHelper.getInstanceForTest(mContext); 292 dbHelper.getWritableDatabase().close(); 293 dbHelper.getReadableDatabase().close(); 294 dbHelper.close(); 295 } 296 } 297