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