Merge "A space-efficient 2D matrix" into sc-dev
This commit is contained in:
@@ -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)) {
|
||||
|
||||
@@ -0,0 +1,562 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.utils;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.Size;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.GrowingArrayUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@link WatchedSparseBooleanMatrix} is an compact NxN array of booleans. The rows and
|
||||
* columns of the array are indexed by integers, which need not be contiguous. The matrix
|
||||
* is square and the row and column indices are identical. This matrix is intended to be
|
||||
* very memory efficient.
|
||||
*
|
||||
* The matrix contains a map from indices to columns: this map requires 2*N integers. The
|
||||
* boolean array is bit-packed and requires N*N/8 bytes. The memory required for an
|
||||
* order-N matrix is therefore 2*N*4 + N*N bytes.
|
||||
*
|
||||
* See {@link SparseBooleanArray} for a discussion of sparse arrays.
|
||||
*/
|
||||
public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappable {
|
||||
|
||||
/**
|
||||
* The matrix is implemented through four arrays. The matrix of booleans is stored in
|
||||
* a one-dimensional {@code mValues} array. {@code mValues} is always of size
|
||||
* {@code mOrder * mOrder}. Elements of {@code mValues} are addressed with
|
||||
* arithmetic: the offset of the element {@code {row, col}} is at
|
||||
* {@code row * mOrder + col}. The term "storage index" applies to {@code mValues}.
|
||||
* A storage index designates a row (column) in the underlying storage. This is not
|
||||
* the same as the row seen by client code.
|
||||
*
|
||||
* Client code addresses the matrix through indices. These are integers that need not
|
||||
* be contiguous. Client indices are mapped to storage indices through two linear
|
||||
* integer arrays. {@code mKeys} is a sorted list of client indices.
|
||||
* {@code mIndices} is a parallel array that contains storage indices. The storage
|
||||
* index of a client index {@code k} is {@code mIndices[i]}, where
|
||||
* {@code mKeys[i] == k}.
|
||||
*
|
||||
* A final array, {@code mInUse} records if storage indices are free or in use. This
|
||||
* array is of size {@code mOrder}. A client index is deleted by removing it from
|
||||
* {@code mKeys} and {@code mIndices} and then setting the original storage index
|
||||
* false in {@code mInUse}.
|
||||
*
|
||||
* Some notes:
|
||||
* <ul>
|
||||
* <li> The matrix never shrinks.
|
||||
* <li> Equality is a very, very expesive operation.
|
||||
* </ul>
|
||||
*/
|
||||
|
||||
/**
|
||||
* mOrder is always a multiple of this value. A minimal matrix therefore holds 2^12
|
||||
* values and requires 1024 bytes.
|
||||
*/
|
||||
private static final int STEP = 64;
|
||||
|
||||
/**
|
||||
* The order of the matrix storage, including any padding. The matrix is always
|
||||
* square. mOrder is always greater than or equal to mSize.
|
||||
*/
|
||||
private int mOrder;
|
||||
|
||||
/**
|
||||
* The number of client keys. This is always less than or equal to mOrder. It is the
|
||||
* order of the matrix as seen by the client.
|
||||
*/
|
||||
private int mSize;
|
||||
|
||||
/**
|
||||
* The in-use list.
|
||||
*/
|
||||
private boolean[] mInUse;
|
||||
|
||||
/**
|
||||
* The array of client keys (indices), in sorted order.
|
||||
*/
|
||||
private int[] mKeys;
|
||||
|
||||
/**
|
||||
* The mapping from a client key to an storage index. If client key K is at index N
|
||||
* in mKeys, then the storage index for K is at mMap[N].
|
||||
*/
|
||||
private int[] mMap;
|
||||
|
||||
/**
|
||||
* The boolean array. This array is always {@code mOrder x mOrder} in size.
|
||||
*/
|
||||
private boolean[] mValues;
|
||||
|
||||
/**
|
||||
* A convenience function called when the elements are added to or removed from the storage.
|
||||
* The watchable is always {@link this}.
|
||||
*/
|
||||
private void onChanged() {
|
||||
dispatchChange(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new WatchedSparseBooleanMatrix containing no mappings.
|
||||
*/
|
||||
public WatchedSparseBooleanMatrix() {
|
||||
this(STEP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SparseBooleanMatrix containing no mappings that will not require any
|
||||
* additional memory allocation to store the specified number of mappings. The
|
||||
* capacity is always rounded up to a non-zero multiple of STEP.
|
||||
*/
|
||||
public WatchedSparseBooleanMatrix(int initialCapacity) {
|
||||
mOrder = initialCapacity;
|
||||
if (mOrder < STEP) {
|
||||
mOrder = STEP;
|
||||
}
|
||||
if (mOrder % STEP != 0) {
|
||||
mOrder = ((initialCapacity / STEP) + 1) * STEP;
|
||||
}
|
||||
if (mOrder < STEP || (mOrder % STEP != 0)) {
|
||||
throw new RuntimeException("mOrder is " + mOrder + " initCap is " + initialCapacity);
|
||||
}
|
||||
|
||||
mInUse = new boolean[mOrder];
|
||||
mKeys = ArrayUtils.newUnpaddedIntArray(mOrder);
|
||||
mMap = ArrayUtils.newUnpaddedIntArray(mOrder);
|
||||
mValues = new boolean[mOrder * mOrder];
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A copy constructor that can be used for snapshotting.
|
||||
*/
|
||||
private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) {
|
||||
mOrder = r.mOrder;
|
||||
mSize = r.mSize;
|
||||
mKeys = r.mKeys.clone();
|
||||
mMap = r.mMap.clone();
|
||||
mInUse = r.mInUse.clone();
|
||||
mValues = r.mValues.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this object.
|
||||
*/
|
||||
public WatchedSparseBooleanMatrix snapshot() {
|
||||
return new WatchedSparseBooleanMatrix(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the boolean mapped from the specified key, or <code>false</code>
|
||||
* if no such mapping has been made.
|
||||
*/
|
||||
public boolean get(int row, int col) {
|
||||
return get(row, col, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the boolean mapped from the specified key, or the specified value
|
||||
* if no such mapping has been made.
|
||||
*/
|
||||
public boolean get(int row, int col, boolean valueIfKeyNotFound) {
|
||||
int r = indexOfKey(row, false);
|
||||
int c = indexOfKey(col, false);
|
||||
if (r >= 0 && c >= 0) {
|
||||
return valueAt(r, c);
|
||||
} else {
|
||||
return valueIfKeyNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping from the specified keys to the specified value, replacing the
|
||||
* previous mapping from the specified keys if there was one.
|
||||
*/
|
||||
public void put(int row, int col, boolean value) {
|
||||
int r = indexOfKey(row);
|
||||
int c = indexOfKey(col);
|
||||
if (r < 0 || c < 0) {
|
||||
// One or both of the keys has not be installed yet. Install them now.
|
||||
// Installing either key may shift the other key. The safest course is to
|
||||
// install the keys that are not present and then recompute both indices.
|
||||
if (r < 0) {
|
||||
r = indexOfKey(row, true);
|
||||
}
|
||||
if (c < 0) {
|
||||
c = indexOfKey(col, true);
|
||||
}
|
||||
r = indexOfKey(row);
|
||||
c = indexOfKey(col);
|
||||
}
|
||||
if (r >= 0 && c >= 0) {
|
||||
setValueAt(r, c, value);
|
||||
onChanged();
|
||||
} else {
|
||||
throw new RuntimeException("matrix overflow");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping from the specified key, if there was any. Note that deletion
|
||||
* applies to a single index, not to an element. The matrix never shrinks but the
|
||||
* space will be reused the next time an index is added.
|
||||
*/
|
||||
public void deleteKey(int key) {
|
||||
int i = indexOfKey(key, false);
|
||||
if (i >= 0) {
|
||||
removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping at the specified index. The matrix does not shrink. This
|
||||
* throws ArrayIndexOutOfBounds if the index out outside the range {@code 0..size()-1}.
|
||||
*/
|
||||
public void removeAt(int index) {
|
||||
validateIndex(index);
|
||||
mInUse[mMap[index]] = false;
|
||||
System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
|
||||
System.arraycopy(mMap, index + 1, mMap, index, mSize - (index + 1));
|
||||
mSize--;
|
||||
onChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value mappings that this WatchedSparseBooleanMatrix
|
||||
* currently stores.
|
||||
*/
|
||||
public int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key-value mappings from this WatchedSparseBooleanMatrix.
|
||||
*/
|
||||
public void clear() {
|
||||
mSize = 0;
|
||||
Arrays.fill(mInUse, false);
|
||||
onChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an index in the range <code>0...size()-1</code>, returns the key from the
|
||||
* <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix stores.
|
||||
*
|
||||
* <p>The keys corresponding to indices in ascending order are guaranteed to be in
|
||||
* ascending order, e.g., <code>keyAt(0)</code> will return the smallest key and
|
||||
* <code>keyAt(size()-1)</code> will return the largest key.</p>
|
||||
*
|
||||
* <p>{@link ArrayIndexOutOfBoundsException} is thrown for indices outside of the
|
||||
* range <code>0...size()-1</code></p>
|
||||
*/
|
||||
public int keyAt(int index) {
|
||||
validateIndex(index);
|
||||
return mKeys[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a row and column, each in the range <code>0...size()-1</code>, returns the
|
||||
* value from the <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix
|
||||
* stores.
|
||||
*/
|
||||
public boolean valueAt(int rowIndex, int colIndex) {
|
||||
validateIndex(rowIndex, colIndex);
|
||||
int r = mMap[rowIndex];
|
||||
int c = mMap[colIndex];
|
||||
int element = r * mOrder + c;
|
||||
return mValues[element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly set the value at a particular index.
|
||||
*/
|
||||
public void setValueAt(int rowIndex, int colIndex, boolean value) {
|
||||
validateIndex(rowIndex, colIndex);
|
||||
int r = mMap[rowIndex];
|
||||
int c = mMap[colIndex];
|
||||
int element = r * mOrder + c;
|
||||
mValues[element] = value;
|
||||
onChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index for which {@link #keyAt} would return the specified key, or a
|
||||
* negative number if the specified key is not mapped.
|
||||
*/
|
||||
public int indexOfKey(int key) {
|
||||
return binarySearch(mKeys, mSize, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the matrix knows the user index.
|
||||
*/
|
||||
public boolean contains(int key) {
|
||||
return indexOfKey(key) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the index of a key. If the key does not exist and grow is true, then add the
|
||||
* key. If the does not exist and grow is false, return -1.
|
||||
*/
|
||||
private int indexOfKey(int key, boolean grow) {
|
||||
int i = binarySearch(mKeys, mSize, key);
|
||||
if (i < 0 && grow) {
|
||||
i = ~i;
|
||||
if (mSize >= mOrder) {
|
||||
// Preemptively grow the matrix, which also grows the free list.
|
||||
growMatrix();
|
||||
}
|
||||
int newIndex = nextFree();
|
||||
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
|
||||
mMap = GrowingArrayUtils.insert(mMap, mSize, i, newIndex);
|
||||
mSize++;
|
||||
// Initialize the row and column corresponding to the new index.
|
||||
for (int n = 0; n < mSize; n++) {
|
||||
mValues[n * mOrder + newIndex] = false;
|
||||
mValues[newIndex * mOrder + n] = false;
|
||||
}
|
||||
onChanged();
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the index. This can throw.
|
||||
*/
|
||||
private void validateIndex(int index) {
|
||||
if (index >= mSize) {
|
||||
// The array might be slightly bigger than mSize, in which case, indexing won't fail.
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate two indices.
|
||||
*/
|
||||
private void validateIndex(int row, int col) {
|
||||
validateIndex(row);
|
||||
validateIndex(col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an unused storage index, mark it in-use, and return it.
|
||||
*/
|
||||
private int nextFree() {
|
||||
for (int i = 0; i < mInUse.length; i++) {
|
||||
if (!mInUse[i]) {
|
||||
mInUse[i] = true;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the 2D array. This also extends the free list.
|
||||
*/
|
||||
private void growMatrix() {
|
||||
int newOrder = mOrder + STEP;
|
||||
|
||||
boolean[] newInuse = Arrays.copyOf(mInUse, newOrder);
|
||||
|
||||
boolean[] newValues = new boolean[newOrder * newOrder];
|
||||
for (int i = 0; i < mOrder; i++) {
|
||||
int row = mOrder * i;
|
||||
int newRow = newOrder * i;
|
||||
for (int j = 0; j < mOrder; j++) {
|
||||
int index = row + j;
|
||||
int newIndex = newRow + j;
|
||||
newValues[newIndex] = mValues[index];
|
||||
}
|
||||
}
|
||||
|
||||
mInUse = newInuse;
|
||||
mValues = newValues;
|
||||
mOrder = newOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = mSize;
|
||||
for (int i = 0; i < mSize; i++) {
|
||||
hashCode = 31 * hashCode + mKeys[i];
|
||||
hashCode = 31 * hashCode + mMap[i];
|
||||
}
|
||||
for (int i = 0; i < mSize; i++) {
|
||||
int row = mMap[i] * mOrder;
|
||||
for (int j = 0; j < mSize; j++) {
|
||||
int element = mMap[j] + row;
|
||||
hashCode = 31 * hashCode + (mValues[element] ? 1 : 0);
|
||||
}
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object that) {
|
||||
if (this == that) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(that instanceof WatchedSparseBooleanMatrix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WatchedSparseBooleanMatrix other = (WatchedSparseBooleanMatrix) that;
|
||||
if (mSize != other.mSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < mSize; i++) {
|
||||
if (mKeys[i] != other.mKeys[i]) {
|
||||
return false;
|
||||
}
|
||||
if (mMap[i] != other.mMap[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < mSize; i++) {
|
||||
int row = mMap[i] * mOrder;
|
||||
for (int j = 0; j < mSize; j++) {
|
||||
int element = mMap[j] + row;
|
||||
if (mValues[element] != other.mValues[element]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the matrix meta information. This is always three strings long.
|
||||
*/
|
||||
private @Size(3) String[] matrixToStringMeta() {
|
||||
String[] result = new String[3];
|
||||
|
||||
StringBuilder k = new StringBuilder();
|
||||
for (int i = 0; i < mSize; i++) {
|
||||
k.append(mKeys[i]);
|
||||
if (i < mSize - 1) {
|
||||
k.append(" ");
|
||||
}
|
||||
}
|
||||
result[0] = k.substring(0);
|
||||
|
||||
StringBuilder m = new StringBuilder();
|
||||
for (int i = 0; i < mSize; i++) {
|
||||
m.append(mMap[i]);
|
||||
if (i < mSize - 1) {
|
||||
m.append(" ");
|
||||
}
|
||||
}
|
||||
result[1] = m.substring(0);
|
||||
|
||||
StringBuilder u = new StringBuilder();
|
||||
for (int i = 0; i < mOrder; i++) {
|
||||
u.append(mInUse[i] ? "1" : "0");
|
||||
}
|
||||
result[2] = u.substring(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the matrix as an array of strings. There is one string per row. Each
|
||||
* string has a '1' or a '0' in the proper column.
|
||||
*/
|
||||
private String[] matrixToStringRaw() {
|
||||
String[] result = new String[mOrder];
|
||||
for (int i = 0; i < mOrder; i++) {
|
||||
int row = i * mOrder;
|
||||
StringBuilder line = new StringBuilder(mOrder);
|
||||
for (int j = 0; j < mOrder; j++) {
|
||||
int element = row + j;
|
||||
line.append(mValues[element] ? "1" : "0");
|
||||
}
|
||||
result[i] = line.substring(0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String[] matrixToStringCooked() {
|
||||
String[] result = new String[mSize];
|
||||
for (int i = 0; i < mSize; i++) {
|
||||
int row = mMap[i] * mOrder;
|
||||
StringBuilder line = new StringBuilder(mSize);
|
||||
for (int j = 0; j < mSize; j++) {
|
||||
int element = row + mMap[j];
|
||||
line.append(mValues[element] ? "1" : "0");
|
||||
}
|
||||
result[i] = line.substring(0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String[] matrixToString(boolean raw) {
|
||||
String[] meta = matrixToStringMeta();
|
||||
String[] data;
|
||||
if (raw) {
|
||||
data = matrixToStringRaw();
|
||||
} else {
|
||||
data = matrixToStringCooked();
|
||||
}
|
||||
String[] result = new String[meta.length + data.length];
|
||||
System.arraycopy(meta, 0, result, 0, meta.length);
|
||||
System.arraycopy(data, 0, result, meta.length, data.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This implementation creates a string that describes the size of the array. A
|
||||
* string with all the values could easily exceed 1Mb.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + mSize + "x" + mSize + "}";
|
||||
}
|
||||
|
||||
// Copied from android.util.ContainerHelpers, which is not visible outside the
|
||||
// android.util package.
|
||||
private static int binarySearch(int[] array, int size, int value) {
|
||||
int lo = 0;
|
||||
int hi = size - 1;
|
||||
|
||||
while (lo <= hi) {
|
||||
final int mid = (lo + hi) >>> 1;
|
||||
final int midVal = array[mid];
|
||||
|
||||
if (midVal < value) {
|
||||
lo = mid + 1;
|
||||
} else if (midVal > value) {
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
return mid; // value found
|
||||
}
|
||||
}
|
||||
return ~lo; // value not present
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user