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

Commit 1396ffee authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "WorkSource: Add support for chaining attribution."

parents 1fe35247 f0202a95
Loading
Loading
Loading
Loading
+316 −18
Original line number Diff line number Diff line
package android.os;

import android.annotation.Nullable;
import android.os.WorkSourceProto;
import android.util.Log;
import android.util.proto.ProtoOutputStream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

/**
 * Describes the source of some work that may be done by someone else.
@@ -19,6 +22,8 @@ public class WorkSource implements Parcelable {
    int[] mUids;
    String[] mNames;

    private ArrayList<WorkChain> mChains;

    /**
     * Internal statics to avoid object allocations in some operations.
     * The WorkSource object itself is not thread safe, but we need to
@@ -39,6 +44,7 @@ public class WorkSource implements Parcelable {
     */
    public WorkSource() {
        mNum = 0;
        mChains = null;
    }

    /**
@@ -48,6 +54,7 @@ public class WorkSource implements Parcelable {
    public WorkSource(WorkSource orig) {
        if (orig == null) {
            mNum = 0;
            mChains = null;
            return;
        }
        mNum = orig.mNum;
@@ -58,6 +65,16 @@ public class WorkSource implements Parcelable {
            mUids = null;
            mNames = null;
        }

        if (orig.mChains != null) {
            // Make a copy of all WorkChains that exist on |orig| since they are mutable.
            mChains = new ArrayList<>(orig.mChains.size());
            for (WorkChain chain : orig.mChains) {
                mChains.add(new WorkChain(chain));
            }
        } else {
            mChains = null;
        }
    }

    /** @hide */
@@ -65,6 +82,7 @@ public class WorkSource implements Parcelable {
        mNum = 1;
        mUids = new int[] { uid, 0 };
        mNames = null;
        mChains = null;
    }

    /** @hide */
@@ -75,12 +93,21 @@ public class WorkSource implements Parcelable {
        mNum = 1;
        mUids = new int[] { uid, 0 };
        mNames = new String[] { name, null };
        mChains = null;
    }

    WorkSource(Parcel in) {
        mNum = in.readInt();
        mUids = in.createIntArray();
        mNames = in.createStringArray();

        int numChains = in.readInt();
        if (numChains > 0) {
            mChains = new ArrayList<>(numChains);
            in.readParcelableList(mChains, WorkChain.class.getClassLoader());
        } else {
            mChains = null;
        }
    }

    /** @hide */
@@ -99,7 +126,8 @@ public class WorkSource implements Parcelable {
    }

    /**
     * Clear names from this WorkSource.  Uids are left intact.
     * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
     * intact.
     *
     * <p>Useful when combining with another WorkSource that doesn't have names.
     * @hide
@@ -127,11 +155,16 @@ public class WorkSource implements Parcelable {
     */
    public void clear() {
        mNum = 0;
        if (mChains != null) {
            mChains.clear();
        }
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof WorkSource && !diff((WorkSource)o);
        return o instanceof WorkSource
            && !diff((WorkSource) o)
            && Objects.equals(mChains, ((WorkSource) o).mChains);
    }

    @Override
@@ -145,6 +178,11 @@ public class WorkSource implements Parcelable {
                result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
            }
        }

        if (mChains != null) {
            result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
        }

        return result;
    }

@@ -153,6 +191,8 @@ public class WorkSource implements Parcelable {
     * @param other The WorkSource to compare against.
     * @return If there is a difference, true is returned.
     */
    // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
    // we keep its semantics the same and ignore any differences in WorkChains (if any).
    public boolean diff(WorkSource other) {
        int N = mNum;
        if (N != other.mNum) {
@@ -175,12 +215,15 @@ public class WorkSource implements Parcelable {

    /**
     * Replace the current contents of this work source with the given
     * work source.  If <var>other</var> is null, the current work source
     * work source.  If {@code other} is null, the current work source
     * will be made empty.
     */
    public void set(WorkSource other) {
        if (other == null) {
            mNum = 0;
            if (mChains != null) {
                mChains.clear();
            }
            return;
        }
        mNum = other.mNum;
@@ -203,6 +246,18 @@ public class WorkSource implements Parcelable {
            mUids = null;
            mNames = null;
        }

        if (other.mChains != null) {
            if (mChains != null) {
                mChains.clear();
            } else {
                mChains = new ArrayList<>(other.mChains.size());
            }

            for (WorkChain chain : other.mChains) {
                mChains.add(new WorkChain(chain));
            }
        }
    }

    /** @hide */
@@ -211,6 +266,7 @@ public class WorkSource implements Parcelable {
        if (mUids == null) mUids = new int[2];
        mUids[0] = uid;
        mNames = null;
        mChains.clear();
    }

    /** @hide */
@@ -225,9 +281,21 @@ public class WorkSource implements Parcelable {
        }
        mUids[0] = uid;
        mNames[0] = name;
        mChains.clear();
    }

    /** @hide */
    /**
     * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
     * differences in chains are returned. This will be removed once its callers have been
     * rewritten.
     *
     * NOTE: This is currently only used in GnssLocationProvider.
     *
     * @hide
     * @deprecated for internal use only. WorkSources are opaque and no external callers should need
     *     to be aware of internal differences.
     */
    @Deprecated
    public WorkSource[] setReturningDiffs(WorkSource other) {
        synchronized (sTmpWorkSource) {
            sNewbWork = null;
@@ -251,11 +319,34 @@ public class WorkSource implements Parcelable {
     */
    public boolean add(WorkSource other) {
        synchronized (sTmpWorkSource) {
            return updateLocked(other, false, false);
            boolean uidAdded = updateLocked(other, false, false);

            boolean chainAdded = false;
            if (other.mChains != null) {
                // NOTE: This is quite an expensive operation, especially if the number of chains
                // is large. We could look into optimizing it if it proves problematic.
                if (mChains == null) {
                    mChains = new ArrayList<>(other.mChains.size());
                }

                for (WorkChain wc : other.mChains) {
                    if (!mChains.contains(wc)) {
                        mChains.add(new WorkChain(wc));
                    }
                }
            }

    /** @hide */
            return uidAdded || chainAdded;
        }
    }

    /**
     * Legacy API: DO NOT USE. Only in use from unit tests.
     *
     * @hide
     * @deprecated meant for unit testing use only. Will be removed in a future API revision.
     */
    @Deprecated
    public WorkSource addReturningNewbs(WorkSource other) {
        synchronized (sTmpWorkSource) {
            sNewbWork = null;
@@ -311,22 +402,14 @@ public class WorkSource implements Parcelable {
        return true;
    }

    /** @hide */
    public WorkSource addReturningNewbs(int uid) {
        synchronized (sTmpWorkSource) {
            sNewbWork = null;
            sTmpWorkSource.mUids[0] = uid;
            updateLocked(sTmpWorkSource, false, true);
            return sNewbWork;
        }
    }

    public boolean remove(WorkSource other) {
        if (mNum <= 0 || other.mNum <= 0) {
            return false;
        }

        boolean uidRemoved = false;
        if (mNames == null && other.mNames == null) {
            return removeUids(other);
            uidRemoved = removeUids(other);
        } else {
            if (mNames == null) {
                throw new IllegalArgumentException("Other " + other + " has names, but target "
@@ -336,8 +419,44 @@ public class WorkSource implements Parcelable {
                throw new IllegalArgumentException("Target " + this + " has names, but other "
                        + other + " does not");
            }
            return removeUidsAndNames(other);
            uidRemoved = removeUidsAndNames(other);
        }

        boolean chainRemoved = false;
        if (other.mChains != null) {
            if (mChains != null) {
                chainRemoved = mChains.removeAll(other.mChains);
            }
        } else if (mChains != null) {
            mChains.clear();
            chainRemoved = true;
        }

        return uidRemoved || chainRemoved;
    }

    /**
     * Create a new {@code WorkChain} associated with this WorkSource and return it.
     *
     * @hide
     */
    public WorkChain createWorkChain() {
        if (mChains == null) {
            mChains = new ArrayList<>(4);
        }

        final WorkChain wc = new WorkChain();
        mChains.add(wc);

        return wc;
    }

    /**
     * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
     * @hide
     */
    public ArrayList<WorkChain> getWorkChains() {
        return mChains;
    }

    private boolean removeUids(WorkSource other) {
@@ -648,6 +767,167 @@ public class WorkSource implements Parcelable {
        }
    }

    /**
     * Represents an attribution chain for an item of work being performed. An attribution chain is
     * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
     * of the work, and the node at the highest index performs the work. Nodes at other indices
     * are intermediaries that facilitate the work. Examples :
     *
     * (1) Work being performed by uid=2456 (no chaining):
     * <pre>
     * WorkChain {
     *   mUids = { 2456 }
     *   mTags = { null }
     *   mSize = 1;
     * }
     * </pre>
     *
     * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
     *
     * <pre>
     * WorkChain {
     *   mUids = { 5678, 2456 }
     *   mTags = { null, "c1" }
     *   mSize = 1
     * }
     * </pre>
     *
     * Attribution chains are mutable, though the only operation that can be performed on them
     * is the addition of a new node at the end of the attribution chain to represent
     *
     * @hide
     */
    public static class WorkChain implements Parcelable {
        private int mSize;
        private int[] mUids;
        private String[] mTags;

        // @VisibleForTesting
        public WorkChain() {
            mSize = 0;
            mUids = new int[4];
            mTags = new String[4];
        }

        // @VisibleForTesting
        public WorkChain(WorkChain other) {
            mSize = other.mSize;
            mUids = other.mUids.clone();
            mTags = other.mTags.clone();
        }

        private WorkChain(Parcel in) {
            mSize = in.readInt();
            mUids = in.createIntArray();
            mTags = in.createStringArray();
        }

        /**
         * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
         * {@code WorkChain}.
         */
        public WorkChain addNode(int uid, @Nullable String tag) {
            if (mSize == mUids.length) {
                resizeArrays();
            }

            mUids[mSize] = uid;
            mTags[mSize] = tag;
            mSize++;

            return this;
        }

        // TODO: The following three trivial getters are purely for testing and will be removed
        // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
        // diffing it etc.
        //
        // @VisibleForTesting
        public int[] getUids() {
            return mUids;
        }
        // @VisibleForTesting
        public String[] getTags() {
            return mTags;
        }
        // @VisibleForTesting
        public int getSize() {
            return mSize;
        }

        private void resizeArrays() {
            final int newSize = mSize * 2;
            int[] uids = new int[newSize];
            String[] tags = new String[newSize];

            System.arraycopy(mUids, 0, uids, 0, mSize);
            System.arraycopy(mTags, 0, tags, 0, mSize);

            mUids = uids;
            mTags = tags;
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder("WorkChain{");
            for (int i = 0; i < mSize; ++i) {
                if (i != 0) {
                    result.append(", ");
                }
                result.append("(");
                result.append(mUids[i]);
                if (mTags[i] != null) {
                    result.append(", ");
                    result.append(mTags[i]);
                }
                result.append(")");
            }

            result.append("}");
            return result.toString();
        }

        @Override
        public int hashCode() {
            return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof WorkChain) {
                WorkChain other = (WorkChain) o;

                return mSize == other.mSize
                    && Arrays.equals(mUids, other.mUids)
                    && Arrays.equals(mTags, other.mTags);
            }

            return false;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mSize);
            dest.writeIntArray(mUids);
            dest.writeStringArray(mTags);
        }

        public static final Parcelable.Creator<WorkChain> CREATOR =
                new Parcelable.Creator<WorkChain>() {
                    public WorkChain createFromParcel(Parcel in) {
                        return new WorkChain(in);
                    }
                    public WorkChain[] newArray(int size) {
                        return new WorkChain[size];
                    }
                };
    }

    @Override
    public int describeContents() {
        return 0;
@@ -658,6 +938,13 @@ public class WorkSource implements Parcelable {
        dest.writeInt(mNum);
        dest.writeIntArray(mUids);
        dest.writeStringArray(mNames);

        if (mChains == null) {
            dest.writeInt(-1);
        } else {
            dest.writeInt(mChains.size());
            dest.writeParcelableList(mChains, flags);
        }
    }

    @Override
@@ -674,6 +961,17 @@ public class WorkSource implements Parcelable {
                result.append(mNames[i]);
            }
        }

        if (mChains != null) {
            result.append(" chains=");
            for (int i = 0; i < mChains.size(); ++i) {
                if (i != 0) {
                    result.append(", ");
                }
                result.append(mChains.get(i));
            }
        }

        result.append("}");
        return result.toString();
    }
+208 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2008 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 android.os;

import android.os.WorkSource.WorkChain;

import junit.framework.TestCase;

import java.util.List;

/**
 * Provides unit tests for hidden / unstable WorkSource APIs that are not CTS testable.
 *
 * These tests will be moved to CTS when finalized.
 */
public class WorkSourceTest extends TestCase {
    public void testWorkChain_add() {
        WorkChain wc1 = new WorkChain();
        wc1.addNode(56, null);

        assertEquals(56, wc1.getUids()[0]);
        assertEquals(null, wc1.getTags()[0]);
        assertEquals(1, wc1.getSize());

        wc1.addNode(57, "foo");
        assertEquals(56, wc1.getUids()[0]);
        assertEquals(null, wc1.getTags()[0]);
        assertEquals(57, wc1.getUids()[1]);
        assertEquals("foo", wc1.getTags()[1]);

        assertEquals(2, wc1.getSize());
    }

    public void testWorkChain_equalsHashCode() {
        WorkChain wc1 = new WorkChain();
        WorkChain wc2 = new WorkChain();

        assertEquals(wc1, wc2);
        assertEquals(wc1.hashCode(), wc2.hashCode());

        wc1.addNode(1, null);
        wc2.addNode(1, null);
        assertEquals(wc1, wc2);
        assertEquals(wc1.hashCode(), wc2.hashCode());

        wc1.addNode(2, "tag");
        wc2.addNode(2, "tag");
        assertEquals(wc1, wc2);
        assertEquals(wc1.hashCode(), wc2.hashCode());

        wc1 = new WorkChain();
        wc2 = new WorkChain();
        wc1.addNode(5, null);
        wc2.addNode(6, null);
        assertFalse(wc1.equals(wc2));
        assertFalse(wc1.hashCode() == wc2.hashCode());

        wc1 = new WorkChain();
        wc2 = new WorkChain();
        wc1.addNode(5, "tag1");
        wc2.addNode(5, "tag2");
        assertFalse(wc1.equals(wc2));
        assertFalse(wc1.hashCode() == wc2.hashCode());
    }

    public void testWorkChain_constructor() {
        WorkChain wc1 = new WorkChain();
        wc1.addNode(1, "foo")
            .addNode(2, null)
            .addNode(3, "baz");

        WorkChain wc2 = new WorkChain(wc1);
        assertEquals(wc1, wc2);

        wc1.addNode(4, "baz");
        assertFalse(wc1.equals(wc2));
    }

    public void testDiff_workChains() {
        WorkSource ws1 = new WorkSource();
        ws1.add(50);
        ws1.createWorkChain().addNode(52, "foo");
        WorkSource ws2 = new WorkSource();
        ws2.add(50);
        ws2.createWorkChain().addNode(60, "bar");

        // Diffs don't take WorkChains into account for the sake of backward compatibility.
        assertFalse(ws1.diff(ws2));
        assertFalse(ws2.diff(ws1));
    }

    public void testEquals_workChains() {
        WorkSource ws1 = new WorkSource();
        ws1.add(50);
        ws1.createWorkChain().addNode(52, "foo");

        WorkSource ws2 = new WorkSource();
        ws2.add(50);
        ws2.createWorkChain().addNode(52, "foo");

        assertEquals(ws1, ws2);

        // Unequal number of WorkChains.
        ws2.createWorkChain().addNode(53, "baz");
        assertFalse(ws1.equals(ws2));

        // Different WorkChain contents.
        WorkSource ws3 = new WorkSource();
        ws3.add(50);
        ws3.createWorkChain().addNode(60, "bar");

        assertFalse(ws1.equals(ws3));
        assertFalse(ws3.equals(ws1));
    }

    public void testWorkSourceParcelling() {
        WorkSource ws = new WorkSource();

        WorkChain wc = ws.createWorkChain();
        wc.addNode(56, "foo");
        wc.addNode(75, "baz");
        WorkChain wc2 = ws.createWorkChain();
        wc2.addNode(20, "foo2");
        wc2.addNode(30, "baz2");

        Parcel p = Parcel.obtain();
        ws.writeToParcel(p, 0);
        p.setDataPosition(0);

        WorkSource unparcelled = WorkSource.CREATOR.createFromParcel(p);

        assertEquals(unparcelled, ws);
    }

    public void testSet_workChains() {
        WorkSource ws1 = new WorkSource();
        ws1.add(50);

        WorkSource ws2 = new WorkSource();
        ws2.add(60);
        WorkChain wc = ws2.createWorkChain();
        wc.addNode(75, "tag");

        ws1.set(ws2);

        // Assert that the WorkChains are copied across correctly to the new WorkSource object.
        List<WorkChain> workChains = ws1.getWorkChains();
        assertEquals(1, workChains.size());

        assertEquals(1, workChains.get(0).getSize());
        assertEquals(75, workChains.get(0).getUids()[0]);
        assertEquals("tag", workChains.get(0).getTags()[0]);

        // Also assert that a deep copy of workchains is made, so the addition of a new WorkChain
        // or the modification of an existing WorkChain has no effect.
        ws2.createWorkChain();
        assertEquals(1, ws1.getWorkChains().size());

        wc.addNode(50, "tag2");
        assertEquals(1, ws1.getWorkChains().size());
        assertEquals(1, ws1.getWorkChains().get(0).getSize());
    }

    public void testSet_nullWorkChain() {
        WorkSource ws = new WorkSource();
        ws.add(60);
        WorkChain wc = ws.createWorkChain();
        wc.addNode(75, "tag");

        ws.set(null);
        assertEquals(0, ws.getWorkChains().size());
    }

    public void testAdd_workChains() {
        WorkSource ws = new WorkSource();
        ws.createWorkChain().addNode(70, "foo");

        WorkSource ws2 = new WorkSource();
        ws2.createWorkChain().addNode(60, "tag");

        ws.add(ws2);

        // Check that the new WorkChain is added to the end of the list.
        List<WorkChain> workChains = ws.getWorkChains();
        assertEquals(2, workChains.size());
        assertEquals(1, workChains.get(1).getSize());
        assertEquals(60, ws.getWorkChains().get(1).getUids()[0]);
        assertEquals("tag", ws.getWorkChains().get(1).getTags()[0]);

        // Adding the same WorkChain twice should be a no-op.
        ws.add(ws2);
        assertEquals(2, workChains.size());
    }
}