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

Commit 7ec1df49 authored by Lee Shombert's avatar Lee Shombert
Browse files

A space-efficient 2D matrix

Bug: Bug: 188447813

WatchedSparseBooleanMatrix is a space-efficient NxN boolean array
indexed by integers (the keys).  The integer indices need not be
contiguous.  The indices apply identically to rows and columns, so
there is only one mapping from index to internal row or column.

Test: atest
 * CtsContentTestCases:IntentFilterTest
 * CtsDynamicMimeHostTestCases
 * CtsRoleTestCases
 * FrameworksServicesTests:UserSystemPackageInstallerTest
 * FrameworksServicesTests:PackageManagerSettingsTests
 * FrameworksServicesTests:PackageManagerServiceTest
 * FrameworksServicesTests:AppsFilterTest
 * FrameworksServicesTests:PackageInstallerSessionTest
 * FrameworksServicesTests:ScanTests
 * UserLifecycleTests#startUser
 * UserLifecycleTests#stopUser
 * UserLifecycleTests#switchUser
 * FrameworksServicesTests:WatcherTest
 * android.appsecurity.cts.EphemeralTest
 * android.appsecurity.cts.InstantAppUserTest

Change-Id: Ideafb5bad6b68913eceb8b1bbd2fb78dc5e82644
parent 33f631ad
Loading
Loading
Loading
Loading
+42 −46
Original line number Diff line number Diff line
@@ -58,10 +58,12 @@ import com.android.server.compat.CompatChange;
import com.android.server.om.OverlayReferenceMapper;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Snapshots;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;
import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedSparseBooleanMatrix;
import com.android.server.utils.Watcher;

import java.io.PrintWriter;
@@ -158,12 +160,21 @@ public class AppsFilter implements Watchable, Snappable {
     * initial scam and is null until {@link #onSystemReady()} is called.
     */
    @GuardedBy("mCacheLock")
    private volatile SparseArray<SparseBooleanArray> mShouldFilterCache;
    private volatile WatchedSparseBooleanMatrix mShouldFilterCache;

    /**
     * A cached snapshot.
     */
    private volatile AppsFilter mSnapshot = null;
    private final SnapshotCache<AppsFilter> mSnapshot;

    private SnapshotCache<AppsFilter> makeCache() {
        return new SnapshotCache<AppsFilter>(this, this) {
            @Override
            public AppsFilter createSnapshot() {
                AppsFilter s = new AppsFilter(mSource);
                return s;
            }};
    }

    /**
     * Watchable machinery
@@ -211,7 +222,6 @@ public class AppsFilter implements Watchable, Snappable {
     */
    @Override
    public void dispatchChange(@Nullable Watchable what) {
        mSnapshot = null;
        mWatchable.dispatchChange(what);
    }

@@ -236,6 +246,7 @@ public class AppsFilter implements Watchable, Snappable {
                overlayProvider);
        mStateProvider = stateProvider;
        mBackgroundExecutor = backgroundExecutor;
        mSnapshot = makeCache();
    }

    /**
@@ -258,8 +269,14 @@ public class AppsFilter implements Watchable, Snappable {
        mSystemSigningDetails = orig.mSystemSigningDetails;
        mProtectedBroadcasts = orig.mProtectedBroadcasts;
        mShouldFilterCache = orig.mShouldFilterCache;
        if (mShouldFilterCache != null) {
            synchronized (orig.mCacheLock) {
                mShouldFilterCache = mShouldFilterCache.snapshot();
            }
        }

        mBackgroundExecutor = null;
        mSnapshot = new SnapshotCache.Sealed<>();
    }

    /**
@@ -268,13 +285,7 @@ public class AppsFilter implements Watchable, Snappable {
     * condition causes the cached snapshot to be cleared asynchronously to this method.
     */
    public AppsFilter snapshot() {
        AppsFilter s = mSnapshot;
        if (s == null) {
            s = new AppsFilter(this);
            s.mWatchable.seal();
            mSnapshot = s;
        }
        return s;
        return mSnapshot.snapshot();
    }

    /**
@@ -636,12 +647,7 @@ public class AppsFilter implements Watchable, Snappable {
            if (mShouldFilterCache != null) {
                // update the cache in a one-off manner since we've got all the information we
                // need.
                SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid);
                if (visibleUids == null) {
                    visibleUids = new SparseBooleanArray();
                    mShouldFilterCache.put(recipientUid, visibleUids);
                }
                visibleUids.put(visibleUid, false);
                mShouldFilterCache.put(recipientUid, visibleUid, false);
            }
        }
        if (changed) {
@@ -813,23 +819,21 @@ public class AppsFilter implements Watchable, Snappable {
        if (mShouldFilterCache == null) {
            return;
        }
        for (int i = mShouldFilterCache.size() - 1; i >= 0; i--) {
        for (int i = 0; i < mShouldFilterCache.size(); i++) {
            if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
                mShouldFilterCache.removeAt(i);
                continue;
            }
            SparseBooleanArray targetSparseArray = mShouldFilterCache.valueAt(i);
            for (int j = targetSparseArray.size() - 1; j >= 0; j--) {
                if (UserHandle.getAppId(targetSparseArray.keyAt(j)) == appId) {
                    targetSparseArray.removeAt(j);
                }
                // The key was deleted so the list of keys has shifted left.  That means i
                // is now pointing at the next key to be examined.  The decrement here and
                // the loop increment together mean that i will be unchanged in the need
                // iteration and will correctly point to the next key to be examined.
                i--;
            }
        }
    }

    private void updateEntireShouldFilterCache() {
        mStateProvider.runWithState((settings, users) -> {
            SparseArray<SparseBooleanArray> cache =
            WatchedSparseBooleanMatrix cache =
                    updateEntireShouldFilterCacheInner(settings, users);
            synchronized (mCacheLock) {
                mShouldFilterCache = cache;
@@ -837,10 +841,10 @@ public class AppsFilter implements Watchable, Snappable {
        });
    }

    private SparseArray<SparseBooleanArray> updateEntireShouldFilterCacheInner(
    private WatchedSparseBooleanMatrix updateEntireShouldFilterCacheInner(
            ArrayMap<String, PackageSetting> settings, UserInfo[] users) {
        SparseArray<SparseBooleanArray> cache =
                new SparseArray<>(users.length * settings.size());
        WatchedSparseBooleanMatrix cache =
                new WatchedSparseBooleanMatrix(users.length * settings.size());
        for (int i = settings.size() - 1; i >= 0; i--) {
            updateShouldFilterCacheForPackage(cache,
                    null /*skipPackage*/, settings.valueAt(i), settings, users, i);
@@ -864,7 +868,7 @@ public class AppsFilter implements Watchable, Snappable {
                    packagesCache.put(settings.keyAt(i), pkg);
                }
            });
            SparseArray<SparseBooleanArray> cache =
            WatchedSparseBooleanMatrix cache =
                    updateEntireShouldFilterCacheInner(settingsCopy, usersRef[0]);
            boolean[] changed = new boolean[1];
            // We have a cache, let's make sure the world hasn't changed out from under us.
@@ -916,7 +920,7 @@ public class AppsFilter implements Watchable, Snappable {
        }
    }

    private void updateShouldFilterCacheForPackage(SparseArray<SparseBooleanArray> cache,
    private void updateShouldFilterCacheForPackage(WatchedSparseBooleanMatrix cache,
            @Nullable String skipPackageName, PackageSetting subjectSetting, ArrayMap<String,
            PackageSetting> allSettings, UserInfo[] allUsers, int maxIndex) {
        for (int i = Math.min(maxIndex, allSettings.size() - 1); i >= 0; i--) {
@@ -935,17 +939,11 @@ public class AppsFilter implements Watchable, Snappable {
                for (int ou = 0; ou < userCount; ou++) {
                    int otherUser = allUsers[ou].id;
                    int subjectUid = UserHandle.getUid(subjectUser, subjectSetting.appId);
                    if (!cache.contains(subjectUid)) {
                        cache.put(subjectUid, new SparseBooleanArray(appxUidCount));
                    }
                    int otherUid = UserHandle.getUid(otherUser, otherSetting.appId);
                    if (!cache.contains(otherUid)) {
                        cache.put(otherUid, new SparseBooleanArray(appxUidCount));
                    }
                    cache.get(subjectUid).put(otherUid,
                    cache.put(subjectUid, otherUid,
                            shouldFilterApplicationInternal(
                                    subjectUid, subjectSetting, otherSetting, otherUser));
                    cache.get(otherUid).put(subjectUid,
                    cache.put(otherUid, subjectUid,
                            shouldFilterApplicationInternal(
                                    otherUid, otherSetting, subjectSetting, subjectUser));
                }
@@ -1198,22 +1196,20 @@ public class AppsFilter implements Watchable, Snappable {
            }
            synchronized (mCacheLock) {
                if (mShouldFilterCache != null) { // use cache
                    SparseBooleanArray shouldFilterTargets = mShouldFilterCache.get(callingUid);
                    final int targetUid = UserHandle.getUid(userId, targetPkgSetting.appId);
                    if (shouldFilterTargets == null) {
                    final int callingIndex = mShouldFilterCache.indexOfKey(callingUid);
                    if (callingIndex < 0) {
                        Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
                                + callingUid);
                        return true;
                    }
                    int indexOfTargetUid = shouldFilterTargets.indexOfKey(targetUid);
                    if (indexOfTargetUid < 0) {
                    final int targetUid = UserHandle.getUid(userId, targetPkgSetting.appId);
                    final int targetIndex = mShouldFilterCache.indexOfKey(targetUid);
                    if (targetIndex < 0) {
                        Slog.w(TAG, "Encountered calling -> target with no cached rules: "
                                + callingUid + " -> " + targetUid);
                        return true;
                    }
                    if (!shouldFilterTargets.valueAt(indexOfTargetUid)) {
                        return false;
                    }
                    return mShouldFilterCache.valueAt(callingIndex, targetIndex);
                } else {
                    if (!shouldFilterApplicationInternal(
                            callingUid, callingSetting, targetPkgSetting, userId)) {
+562 −0

File added.

Preview size limit exceeded, changes collapsed.

+92 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static org.junit.Assert.fail;

import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -34,9 +35,12 @@ import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;

/**
 * Test class for {@link Watcher}, {@link Watchable}, {@link WatchableImpl},
 * Test class for various utility classes that support the Watchable or Snappable
 * features.  This covers {@link Watcher}, {@link Watchable}, {@link WatchableImpl},
 * {@link WatchedArrayMap}, {@link WatchedSparseArray}, and
 * {@link WatchedSparseBooleanArray}.
 *
@@ -858,6 +862,93 @@ public class WatcherTest {
        }
    }

    private static class IndexGenerator {
        private final int mSeed;
        private final Random mRandom;
        public IndexGenerator(int seed) {
            mSeed = seed;
            mRandom = new Random(mSeed);
        }
        public int index() {
            return mRandom.nextInt(50000);
        }
        public void reset() {
            mRandom.setSeed(mSeed);
        }
    }

    // Return a value based on the row and column.  The algorithm tries to avoid simple
    // patterns like checkerboard.
    private final boolean cellValue(int row, int col) {
        return (((row * 4 + col) % 3)& 1) == 1;
    }

    // This is an inefficient way to know if a value appears in an array.
    private final boolean contains(int[] s, int length, int k) {
        for (int i = 0; i < length; i++) {
            if (s[i] == k) {
                return true;
            }
        }
        return false;
    }

    private void matrixTest(WatchedSparseBooleanMatrix matrix, int size, IndexGenerator indexer) {
        indexer.reset();
        int[] indexes = new int[size];
        for (int i = 0; i < size; i++) {
            int key = indexer.index();
            // Ensure the list of indices are unique.
            while (contains(indexes, i, key)) {
                key = indexer.index();
            }
            indexes[i] = key;
        }
        // Set values in the matrix.
        for (int i = 0; i < size; i++) {
            int row = indexes[i];
            for (int j = 0; j < size; j++) {
                int col = indexes[j];
                boolean want = cellValue(i, j);
                matrix.put(row, col, want);
            }
        }

        assertEquals(matrix.size(), size);

        // Read back and verify
        for (int i = 0; i < matrix.size(); i++) {
            int row = indexes[i];
            for (int j = 0; j < matrix.size(); j++) {
                int col = indexes[j];
                boolean want = cellValue(i, j);
                boolean actual = matrix.get(row, col);
                String msg = String.format("matrix(%d:%d, %d:%d) == %s, expected %s",
                                           i, row, j, col, actual, want);
                assertEquals(msg, actual, want);
            }
        }

        // Test the keyAt/indexOfKey methods
        for (int i = 0; i < matrix.size(); i++) {
            int key = indexes[i];
            assertEquals(matrix.keyAt(matrix.indexOfKey(key)), key);
        }
    }

    @Test
    public void testWatchedSparseBooleanMatrix() {
        final String name = "WatchedSparseBooleanMatrix";

        // The first part of this method tests the core matrix functionality.  The second
        // part tests the watchable behavior.  The third part tests the snappable
        // behavior.
        IndexGenerator indexer = new IndexGenerator(3);
        matrixTest(new WatchedSparseBooleanMatrix(), 10, indexer);
        matrixTest(new WatchedSparseBooleanMatrix(1000), 500, indexer);
        matrixTest(new WatchedSparseBooleanMatrix(1000), 2000, indexer);
    }

    @Test
    public void testNestedArrays() {
        final String name = "NestedArrays";