summaryrefslogtreecommitdiff
path: root/adservices/service-core/java/com/android/adservices/service/appsearch/AppSearchMeasurementRollbackWorker.java
blob: b282cfefa1a4b92fb7091fc85f1b11e60449a299 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.adservices.service.appsearch;

import android.annotation.NonNull;
import android.app.adservices.AdServicesManager;
import android.content.Context;
import android.os.Build;
import android.util.Pair;

import androidx.annotation.RequiresApi;
import androidx.appsearch.app.AppSearchSession;
import androidx.appsearch.platformstorage.PlatformStorage;

import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.common.compat.FileCompatUtils;
import com.android.adservices.service.consent.ConsentConstants;
import com.android.adservices.service.measurement.rollback.MeasurementRollbackWorker;
import com.android.internal.annotations.VisibleForTesting;

import com.google.common.util.concurrent.ListenableFuture;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * This class provides an interface to read/write measurement rollback data to AppSearch. This is
 * read when the measurement API starts up, and is used to determine whether a rollback happened and
 * the measurement db needs to be cleared.
 */
// TODO(b/269798827): Enable for R.
@RequiresApi(Build.VERSION_CODES.S)
public final class AppSearchMeasurementRollbackWorker implements MeasurementRollbackWorker<String> {
    private static final String DATABASE_NAME =
            FileCompatUtils.getAdservicesFilename("measurement_rollback");

    // At the worker level, we ensure that writes do not conflict with any other writes/reads.
    private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();

    // Timeout for AppSearch write query in milliseconds.
    private final int mTimeoutMs;

    private final String mUserId;
    private final String mAdServicesPackageName;
    private final ListenableFuture<AppSearchSession> mSearchSession;
    private final Executor mExecutor = AdServicesExecutors.getBackgroundExecutor();

    private AppSearchMeasurementRollbackWorker(@NonNull Context context, @NonNull String userId) {
        Objects.requireNonNull(context);
        Objects.requireNonNull(userId);

        mUserId = userId;
        mAdServicesPackageName = AppSearchConsentWorker.getAdServicesPackageName(context);
        mSearchSession =
                PlatformStorage.createSearchSessionAsync(
                        new PlatformStorage.SearchContext.Builder(context, DATABASE_NAME).build());

        mTimeoutMs = FlagsFactory.getFlags().getAppSearchWriteTimeout();
    }

    /** Return an instance of {@link AppSearchMeasurementRollbackWorker} */
    public static AppSearchMeasurementRollbackWorker getInstance(
            @NonNull Context context, @NonNull String userId) {
        Objects.requireNonNull(context);
        Objects.requireNonNull(userId);
        return new AppSearchMeasurementRollbackWorker(context, userId);
    }

    @Override
    public void recordAdServicesDeletionOccurred(
            @AdServicesManager.DeletionApiType int deletionApiType, long currentApexVersion) {
        READ_WRITE_LOCK.writeLock().lock();
        try {
            AppSearchMeasurementRollbackDao dao =
                    createAppSearchMeasurementRollbackDao(deletionApiType, currentApexVersion);
            // Recording measurement deletion is only useful within Android S, since the package
            // name changes from T+. This causes the entire measurement DB to be inaccessible on OTA
            // to T. As a result, the written data doesn't need to be preserved across an OTA, so we
            // don't need to share it with the T package. Thus, we can send an empty list for the
            // packageIdentifiers parameter.
            dao.writeData(mSearchSession, List.of(), mExecutor)
                    .get(mTimeoutMs, TimeUnit.MILLISECONDS);
            LogUtil.d("Wrote measurement rollback data to AppSearch: %s", dao);
        } catch (InterruptedException | TimeoutException | ExecutionException e) {
            LogUtil.e(e, "Failed to write measurement rollback to AppSearch");
            throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
        } finally {
            READ_WRITE_LOCK.writeLock().unlock();
        }
    }

    @VisibleForTesting
    AppSearchMeasurementRollbackDao createAppSearchMeasurementRollbackDao(
            @AdServicesManager.DeletionApiType int deletionApiType, long currentApexVersion) {
        return new AppSearchMeasurementRollbackDao(
                AppSearchMeasurementRollbackDao.getRowId(mUserId, deletionApiType),
                AppSearchMeasurementRollbackDao.NAMESPACE,
                mUserId,
                currentApexVersion);
    }

    @Override
    public void clearAdServicesDeletionOccurred(@NonNull String storageIdentifier) {
        Objects.requireNonNull(storageIdentifier);

        READ_WRITE_LOCK.writeLock().lock();
        try {
            AppSearchDao.deleteData(
                            AppSearchMeasurementRollbackDao.class,
                            mSearchSession,
                            mExecutor,
                            storageIdentifier,
                            AppSearchMeasurementRollbackDao.NAMESPACE)
                    .get(mTimeoutMs, TimeUnit.MILLISECONDS);
            LogUtil.d("Deleted MeasurementRollback data from AppSearch for: %s", storageIdentifier);
        } catch (InterruptedException | TimeoutException | ExecutionException e) {
            LogUtil.e(e, "Failed to delete MeasurementRollback data in AppSearch");
            throw new RuntimeException(ConsentConstants.ERROR_MESSAGE_APPSEARCH_FAILURE);
        } finally {
            READ_WRITE_LOCK.writeLock().unlock();
        }
    }

    @Override
    public Pair<Long, String> getAdServicesDeletionRollbackMetadata(
            @AdServicesManager.DeletionApiType int deletionApiType) {
        READ_WRITE_LOCK.writeLock().lock();
        try {
            AppSearchMeasurementRollbackDao dao =
                    AppSearchMeasurementRollbackDao.readDocument(
                            mSearchSession, mExecutor, mUserId, mAdServicesPackageName);
            LogUtil.d("Result of query for AppSearchMeasurementRollbackDao: %s", dao);
            return dao == null ? null : Pair.create(dao.getApexVersion(), dao.getId());
        } finally {
            READ_WRITE_LOCK.writeLock().unlock();
        }
    }
}