Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 658c3fe6 authored by mrulhania's avatar mrulhania Committed by Manjeet Rulhania
Browse files

Create interface for discrete ops persistence layer

No behavior/functional change.

Bug: 377584611
Test: DiscreteAppOpPersistenceTest
Flag: EXEMPT refactor

Change-Id: I19fac99339c8a70bd9ec250f9b70f62d6b3aa44c
parent 6988e7a4
Loading
Loading
Loading
Loading
+6 −5
Original line number Original line Diff line number Diff line
@@ -113,7 +113,7 @@ final class AttributedOp {
        mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
        mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
                parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
                AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
                AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
                DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
                DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
    }
    }


    /**
    /**
@@ -257,7 +257,8 @@ final class AttributedOp {
        if (isStarted) {
        if (isStarted) {
            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                    parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
                    parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
                    attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1);
                    attributionFlags, attributionChainId,
                    DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1);
        }
        }
    }
    }


@@ -344,8 +345,8 @@ final class AttributedOp {
                    parent.packageName, persistentDeviceId, tag, event.getUidState(),
                    parent.packageName, persistentDeviceId, tag, event.getUidState(),
                    event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
                    event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
                    event.getAttributionFlags(), event.getAttributionChainId(),
                    event.getAttributionFlags(), event.getAttributionChainId(),
                    isPausing ? DiscreteRegistry.ACCESS_TYPE_PAUSE_OP
                    isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP
                            : DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
                            : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP);


            if (!isPausing) {
            if (!isPausing) {
                mAppOpsService.mInProgressStartOpEventPool.release(event);
                mAppOpsService.mInProgressStartOpEventPool.release(event);
@@ -453,7 +454,7 @@ final class AttributedOp {
            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
            mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
                    parent.packageName, persistentDeviceId, tag, event.getUidState(),
                    parent.packageName, persistentDeviceId, tag, event.getUidState(),
                    event.getFlags(), startTime, event.getAttributionFlags(),
                    event.getFlags(), startTime, event.getAttributionFlags(),
                    event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1);
                    event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1);
            if (shouldSendActive) {
            if (shouldSendActive) {
                mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
                        parent.packageName, tag, event.getVirtualDeviceId(), true,
                        parent.packageName, tag, event.getVirtualDeviceId(), true,
+270 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.server.appop;

import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
import static android.app.AppOpsManager.OP_READ_ICC_SMS;
import static android.app.AppOpsManager.OP_READ_SMS;
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
import static android.app.AppOpsManager.OP_SEND_SMS;
import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
import static android.app.AppOpsManager.OP_WRITE_SMS;

import static java.lang.Long.min;
import static java.lang.Math.max;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.os.AsyncTask;
import android.os.Build;
import android.provider.DeviceConfig;
import android.util.Slog;

import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
import java.util.Set;

/**
 * This class provides interface for xml and sqlite implementation. Implementation manages
 * information about recent accesses to ops for permission usage timeline.
 * <p>
 * The discrete history is kept for limited time (initial default is 24 hours, set in
 * {@link DiscreteOpsRegistry#sDiscreteHistoryCutoff} and discarded after that.
 * <p>
 * Discrete history is quantized to reduce resources footprint. By default, quantization is set to
 * one minute in {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. All access times are
 * aligned to the closest quantized time. All durations (except -1, meaning no duration) are
 * rounded up to the closest quantized interval.
 * <p>
 * When data is queried through API, events are deduplicated and for every time quant there can
 * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about
 * different accesses which happened in specified time quant - across dimensions of
 * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension
 * it is only possible to know if at least one access happened in the time quant.
 * <p>
 * INITIALIZATION: We can initialize persistence only after the system is ready
 * as we need to check the optional configuration override from the settings
 * database which is not initialized at the time the app ops service is created. This class
 * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All
 * outside calls are going through {@link HistoricalRegistry}.
 *
 */
abstract class DiscreteOpsRegistry {
    private static final String TAG = DiscreteOpsRegistry.class.getSimpleName();

    static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis";
    static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION =
            "discrete_history_quantization_millis";
    static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
    static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
    static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
            + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
            + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
            + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
            + "," + OP_RESERVED_FOR_TESTING;
    static final int[] sDiscreteOpsToLog =
            new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
                    OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
                    OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
                    OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
                    OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
                    OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
            };

    static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
    static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
    // The duration for which the data is kept, default is 7 days and max 30 days enforced.
    static long sDiscreteHistoryCutoff;

    static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = Duration.ofMinutes(1).toMillis();
    // discrete ops are rounded up to quantization time, meaning we record one op per time bucket
    // in case of duplicate op events.
    static long sDiscreteHistoryQuantization;

    static int[] sDiscreteOps;
    static int sDiscreteFlags;

    static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED
            | OP_FLAG_TRUSTED_PROXY;

    boolean mDebugMode = false;

    static final int ACCESS_TYPE_NOTE_OP =
            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
    static final int ACCESS_TYPE_START_OP =
            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
    static final int ACCESS_TYPE_FINISH_OP =
            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
    static final int ACCESS_TYPE_PAUSE_OP =
            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
    static final int ACCESS_TYPE_RESUME_OP =
            FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
            ACCESS_TYPE_NOTE_OP,
            ACCESS_TYPE_START_OP,
            ACCESS_TYPE_FINISH_OP,
            ACCESS_TYPE_PAUSE_OP,
            ACCESS_TYPE_RESUME_OP
    })
    @interface AccessType {}

    void systemReady() {
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
                AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> {
                    setDiscreteHistoryParameters(p);
                });
        setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY));
    }

    abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId,
            int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
            @AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
            @DiscreteOpsRegistry.AccessType int accessType);

    /**
     * A periodic callback from {@link AppOpsService} to flush the in memory events to disk.
     * The shutdown callback is also plugged into it.
     */
    abstract void writeAndClearAccessHistory();

    /** Remove all discrete op events. */
    abstract void clearHistory();

    /** Remove all discrete op events for given UID and package. */
    abstract void clearHistory(int uid, String packageName);

    /**
     * Offset access time by given timestamp, new access time would be accessTime - offsetMillis.
     */
    abstract void offsetHistory(long offset);

    abstract  void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
            long beginTimeMillis, long endTimeMillis,
            @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
            @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
            @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter,
            Set<String> attributionExemptPkgs);

    abstract void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter,
            @Nullable String attributionTagFilter,
            @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
            int nDiscreteOps);

    void setDebugMode(boolean debugMode) {
        this.mDebugMode = debugMode;
    }

    static long discretizeTimeStamp(long timeStamp) {
        return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;

    }

    static long discretizeDuration(long duration) {
        return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
                / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
    }

    static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) {
        if (!ArrayUtils.contains(sDiscreteOps, op)) {
            return false;
        }
        if ((flags & (sDiscreteFlags)) == 0) {
            return false;
        }
        return true;
    }

    // could this be impl detail of discrete registry, just one test is using the method
    // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps();

    private void setDiscreteHistoryParameters(DeviceConfig.Properties p) {
        if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) {
            sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF,
                    DEFAULT_DISCRETE_HISTORY_CUTOFF);
            if (!Build.IS_DEBUGGABLE && !mDebugMode) {
                sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF,
                        sDiscreteHistoryCutoff);
            }
        } else {
            sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF;
        }
        if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) {
            sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION,
                    DEFAULT_DISCRETE_HISTORY_QUANTIZATION);
            if (!Build.IS_DEBUGGABLE && !mDebugMode) {
                sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION,
                        sDiscreteHistoryQuantization);
            }
        } else {
            sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION;
        }
        sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags =
                p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE;
        sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList(
                p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList(
                DEFAULT_DISCRETE_OPS);
    }

    private static int[] parseOpsList(String opsList) {
        String[] strArr;
        if (opsList.isEmpty()) {
            strArr = new String[0];
        } else {
            strArr = opsList.split(",");
        }
        int nOps = strArr.length;
        int[] result = new int[nOps];
        try {
            for (int i = 0; i < nOps; i++) {
                result[i] = Integer.parseInt(strArr[i]);
            }
        } catch (NumberFormatException e) {
            Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage());
            return parseOpsList(DEFAULT_DISCRETE_OPS);
        }
        return result;
    }
}
+15 −202

File changed and moved.

Preview size limit exceeded, changes collapsed.

+4 −4
Original line number Original line Diff line number Diff line
@@ -135,7 +135,7 @@ final class HistoricalRegistry {
    private static final String PARAMETER_DELIMITER = ",";
    private static final String PARAMETER_DELIMITER = ",";
    private static final String PARAMETER_ASSIGNMENT = "=";
    private static final String PARAMETER_ASSIGNMENT = "=";


    private volatile @NonNull DiscreteRegistry mDiscreteRegistry;
    private volatile @NonNull DiscreteOpsRegistry mDiscreteRegistry;


    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
    private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
@@ -198,7 +198,7 @@ final class HistoricalRegistry {


    HistoricalRegistry(@NonNull Object lock) {
    HistoricalRegistry(@NonNull Object lock) {
        mInMemoryLock = lock;
        mInMemoryLock = lock;
        mDiscreteRegistry = new DiscreteRegistry(lock);
        mDiscreteRegistry = new DiscreteOpsXmlRegistry(lock);
    }
    }


    HistoricalRegistry(@NonNull HistoricalRegistry other) {
    HistoricalRegistry(@NonNull HistoricalRegistry other) {
@@ -475,7 +475,7 @@ final class HistoricalRegistry {
            @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
            @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
            @OpFlags int flags, long accessTime,
            @OpFlags int flags, long accessTime,
            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
            @DiscreteRegistry.AccessType int accessType, int accessCount) {
            @DiscreteOpsRegistry.AccessType int accessType, int accessCount) {
        synchronized (mInMemoryLock) {
        synchronized (mInMemoryLock) {
            if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
            if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                if (!isPersistenceInitializedMLocked()) {
                if (!isPersistenceInitializedMLocked()) {
@@ -512,7 +512,7 @@ final class HistoricalRegistry {
            @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
            @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
            @OpFlags int flags, long eventStartTime, long increment,
            @OpFlags int flags, long eventStartTime, long increment,
            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
            @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
            @DiscreteRegistry.AccessType int accessType) {
            @DiscreteOpsRegistry.AccessType int accessType) {
        synchronized (mInMemoryLock) {
        synchronized (mInMemoryLock) {
            if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
            if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                if (!isPersistenceInitializedMLocked()) {
                if (!isPersistenceInitializedMLocked()) {
+15 −11
Original line number Original line Diff line number Diff line
@@ -47,9 +47,12 @@ import org.junit.runner.RunWith;
import java.io.File;
import java.io.File;
import java.util.List;
import java.util.List;


/**
 * Test xml persistence implementation for discrete ops.
 */
@RunWith(AndroidJUnit4.class)
@RunWith(AndroidJUnit4.class)
public class DiscreteAppOpPersistenceTest {
public class DiscreteAppOpPersistenceTest {
    private DiscreteRegistry mDiscreteRegistry;
    private DiscreteOpsXmlRegistry mDiscreteRegistry;
    private final Object mLock = new Object();
    private final Object mLock = new Object();
    private File mMockDataDirectory;
    private File mMockDataDirectory;
    private final Context mContext =
    private final Context mContext =
@@ -61,7 +64,7 @@ public class DiscreteAppOpPersistenceTest {
    @Before
    @Before
    public void setUp() {
    public void setUp() {
        mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE);
        mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE);
        mDiscreteRegistry = new DiscreteRegistry(mLock, mMockDataDirectory);
        mDiscreteRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory);
        mDiscreteRegistry.systemReady();
        mDiscreteRegistry.systemReady();
    }
    }


@@ -87,7 +90,7 @@ public class DiscreteAppOpPersistenceTest {


        mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
        mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
                uidState, accessTime, duration, attributionFlags, attributionChainId,
                uidState, accessTime, duration, attributionFlags, attributionChainId,
                DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
                DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP);


        // Verify in-memory object is correct
        // Verify in-memory object is correct
        fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
        fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
@@ -119,7 +122,7 @@ public class DiscreteAppOpPersistenceTest {


        mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
        mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
                uidState, accessTime, duration, attributionFlags, attributionChainId,
                uidState, accessTime, duration, attributionFlags, attributionChainId,
                DiscreteRegistry.ACCESS_TYPE_START_OP);
                DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP);


        fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
        fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
                duration, uidState, opFlags, attributionFlags, attributionChainId);
                duration, uidState, opFlags, attributionFlags, attributionChainId);
@@ -136,30 +139,31 @@ public class DiscreteAppOpPersistenceTest {
            int expectedOp, String expectedDeviceId, String expectedAttrTag,
            int expectedOp, String expectedDeviceId, String expectedAttrTag,
            long expectedAccessTime, long expectedAccessDuration, int expectedUidState,
            long expectedAccessTime, long expectedAccessDuration, int expectedUidState,
            int expectedOpFlags, int expectedAttrFlags, int expectedAttrChainId) {
            int expectedOpFlags, int expectedAttrFlags, int expectedAttrChainId) {
        DiscreteRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps();
        DiscreteOpsXmlRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps();


        assertThat(discreteOps.isEmpty()).isFalse();
        assertThat(discreteOps.isEmpty()).isFalse();
        assertThat(discreteOps.mUids.size()).isEqualTo(1);
        assertThat(discreteOps.mUids.size()).isEqualTo(1);


        DiscreteRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid);
        DiscreteOpsXmlRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid);
        assertThat(discreteUidOps.mPackages.size()).isEqualTo(1);
        assertThat(discreteUidOps.mPackages.size()).isEqualTo(1);


        DiscreteRegistry.DiscretePackageOps discretePackageOps =
        DiscreteOpsXmlRegistry.DiscretePackageOps discretePackageOps =
                discreteUidOps.mPackages.get(expectedPackageName);
                discreteUidOps.mPackages.get(expectedPackageName);
        assertThat(discretePackageOps.mPackageOps.size()).isEqualTo(1);
        assertThat(discretePackageOps.mPackageOps.size()).isEqualTo(1);


        DiscreteRegistry.DiscreteOp discreteOp = discretePackageOps.mPackageOps.get(expectedOp);
        DiscreteOpsXmlRegistry.DiscreteOp discreteOp =
                discretePackageOps.mPackageOps.get(expectedOp);
        assertThat(discreteOp.mDeviceAttributedOps.size()).isEqualTo(1);
        assertThat(discreteOp.mDeviceAttributedOps.size()).isEqualTo(1);


        DiscreteRegistry.DiscreteDeviceOp discreteDeviceOp =
        DiscreteOpsXmlRegistry.DiscreteDeviceOp discreteDeviceOp =
                discreteOp.mDeviceAttributedOps.get(expectedDeviceId);
                discreteOp.mDeviceAttributedOps.get(expectedDeviceId);
        assertThat(discreteDeviceOp.mAttributedOps.size()).isEqualTo(1);
        assertThat(discreteDeviceOp.mAttributedOps.size()).isEqualTo(1);


        List<DiscreteRegistry.DiscreteOpEvent> discreteOpEvents =
        List<DiscreteOpsXmlRegistry.DiscreteOpEvent> discreteOpEvents =
                discreteDeviceOp.mAttributedOps.get(expectedAttrTag);
                discreteDeviceOp.mAttributedOps.get(expectedAttrTag);
        assertThat(discreteOpEvents.size()).isEqualTo(1);
        assertThat(discreteOpEvents.size()).isEqualTo(1);


        DiscreteRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0);
        DiscreteOpsXmlRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0);
        assertThat(discreteOpEvent.mNoteTime).isEqualTo(expectedAccessTime);
        assertThat(discreteOpEvent.mNoteTime).isEqualTo(expectedAccessTime);
        assertThat(discreteOpEvent.mNoteDuration).isEqualTo(expectedAccessDuration);
        assertThat(discreteOpEvent.mNoteDuration).isEqualTo(expectedAccessDuration);
        assertThat(discreteOpEvent.mUidState).isEqualTo(expectedUidState);
        assertThat(discreteOpEvent.mUidState).isEqualTo(expectedUidState);