All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.os.WorkSource Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
package android.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.Log;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Describes the source of some work that may be done by someone else.
 * Currently the public representation of what a work source is not
 * defined; this is an opaque container.
 */
public class WorkSource implements Parcelable {
    static final String TAG = "WorkSource";
    static final boolean DEBUG = false;

    @UnsupportedAppUsage
    int mNum;
    @UnsupportedAppUsage
    int[] mUids;
    @UnsupportedAppUsage
    String[] mNames;

    private ArrayList mChains;

    /**
     * Internal statics to avoid object allocations in some operations.
     * The WorkSource object itself is not thread safe, but we need to
     * hold sTmpWorkSource lock while working with these statics.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    static final WorkSource sTmpWorkSource = new WorkSource(0);
    /**
     * For returning newbie work from a modification operation.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    static WorkSource sNewbWork;
    /**
     * For returning gone work from a modification operation.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    static WorkSource sGoneWork;

    /**
     * Create an empty work source.
     */
    public WorkSource() {
        mNum = 0;
        mChains = null;
    }

    /**
     * Create a new WorkSource that is a copy of an existing one.
     * If orig is null, an empty WorkSource is created.
     */
    public WorkSource(WorkSource orig) {
        if (orig == null) {
            mNum = 0;
            mChains = null;
            return;
        }
        mNum = orig.mNum;
        if (orig.mUids != null) {
            mUids = orig.mUids.clone();
            mNames = orig.mNames != null ? orig.mNames.clone() : null;
        } else {
            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;
        }
    }


    /**
     * Creates a work source with the given uid.
     * @param uid the uid performing the work
     * @hide
     */
    @SystemApi
    public WorkSource(int uid) {
        mNum = 1;
        mUids = new int[] { uid, 0 };
        mNames = null;
        mChains = null;
    }

    /**
     * Creates a work source with the given uid and package name.
     * @param uid the uid performing the work
     * @param packageName the package performing the work
     * @hide
     */
    @SystemApi
    public WorkSource(int uid, @NonNull String packageName) {
        Preconditions.checkNotNull(packageName, "packageName can't be null");
        mNum = 1;
        mUids = new int[] { uid, 0 };
        mNames = new String[] { packageName, null };
        mChains = null;
    }

    @UnsupportedAppUsage
    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(), android.os.WorkSource.WorkChain.class);
        } else {
            mChains = null;
        }
    }

    /**
     * Whether system services should create {@code WorkChains} (wherever possible) in the place
     * of flat UID lists.
     *
     * @hide
     */
    public static boolean isChainedBatteryAttributionEnabled(Context context) {
        return Settings.Global.getInt(context.getContentResolver(),
                Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1;
    }

    /**
     * Returns the number of uids in this work source.
     * @hide
     */
    @SystemApi
    public int size() {
        return mNum;
    }

    /**
     * @deprecated use {{@link #getUid(int)}} instead.
     * @hide
     */
    @UnsupportedAppUsage
    @Deprecated
    public int get(int index) {
        return getUid(index);
    }

    /**
     * Get the uid at the given index.
     * If {@code index} < 0 or {@code index} >= {@link #size() N}, then the behavior is undefined.
     * @hide
     */
    @SystemApi
    public int getUid(int index) {
        return mUids[index];
    }

    /**
     * Return the UID to which this WorkSource should be attributed to, i.e, the UID that
     * initiated the work and not the UID performing it. If the WorkSource has no UIDs, returns -1
     * instead.
     *
     * @hide
     */
    public int getAttributionUid() {
        if (isEmpty()) {
            return -1;
        }

        return mNum > 0 ? mUids[0] : mChains.get(0).getAttributionUid();
    }

    /**
     * @deprecated use {{@link #getPackageName(int)}} instead.
     * @hide
     */
    @UnsupportedAppUsage
    @Deprecated
    public String getName(int index) {
        return getPackageName(index);
    }

    /**
     * Get the package name at the given index.
     * If {@code index} < 0 or {@code index} >= {@link #size() N}, then the behavior is undefined.
     * @hide
     */
    @SystemApi
    @Nullable
    public String getPackageName(int index) {
        return mNames != null ? mNames[index] : null;
    }

    /**
     * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
     * intact.
     *
     * 

Useful when combining with another WorkSource that doesn't have names. */ private void clearNames() { if (mNames != null) { mNames = null; // Clear out any duplicate uids now that we don't have names to disambiguate them. int destIndex = 1; int newNum = mNum; for (int sourceIndex = 1; sourceIndex < mNum; sourceIndex++) { if (mUids[sourceIndex] == mUids[sourceIndex - 1]) { newNum--; } else { mUids[destIndex] = mUids[sourceIndex]; destIndex++; } } mNum = newNum; } } /** * Clear this WorkSource to be empty. */ public void clear() { mNum = 0; if (mChains != null) { mChains.clear(); } } @Override public boolean equals(@Nullable Object o) { if (o instanceof WorkSource) { WorkSource other = (WorkSource) o; if (diff(other)) { return false; } if (mChains != null && !mChains.isEmpty()) { return mChains.equals(other.mChains); } else { return other.mChains == null || other.mChains.isEmpty(); } } return false; } @Override public int hashCode() { int result = 0; for (int i = 0; i < mNum; i++) { result = ((result << 4) | (result >>> 28)) ^ mUids[i]; } if (mNames != null) { for (int i = 0; i < mNum; i++) { result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode(); } } if (mChains != null) { result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode(); } return result; } /** * Compare this WorkSource with another. * @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) { return true; } final int[] uids1 = mUids; final int[] uids2 = other.mUids; final String[] names1 = mNames; final String[] names2 = other.mNames; for (int i=0; i= mNum) { System.arraycopy(other.mUids, 0, mUids, 0, mNum); } else { mUids = other.mUids.clone(); } if (other.mNames != null) { if (mNames != null && mNames.length >= mNum) { System.arraycopy(other.mNames, 0, mNames, 0, mNum); } else { mNames = other.mNames.clone(); } } else { mNames = null; } } else { 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 */ public void set(int uid) { mNum = 1; if (mUids == null) mUids = new int[2]; mUids[0] = uid; mNames = null; if (mChains != null) { mChains.clear(); } } /** @hide */ public void set(int uid, String name) { if (name == null) { throw new NullPointerException("Name can't be null"); } mNum = 1; if (mUids == null) { mUids = new int[2]; mNames = new String[2]; } mUids[0] = uid; mNames[0] = name; if (mChains != null) { mChains.clear(); } } /** * 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 @TestApi public WorkSource[] setReturningDiffs(WorkSource other) { synchronized (sTmpWorkSource) { sNewbWork = null; sGoneWork = null; updateLocked(other, true, true); if (sNewbWork != null || sGoneWork != null) { WorkSource[] diffs = new WorkSource[2]; diffs[0] = sNewbWork; diffs[1] = sGoneWork; return diffs; } return null; } } /** * Merge the contents of other WorkSource in to this one. * * @param other The other WorkSource whose contents are to be merged. * @return Returns true if any new sources were added. */ public boolean add(WorkSource other) { synchronized (sTmpWorkSource) { 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)); chainAdded = true; } } } return uidAdded || chainAdded; } } /** * Returns a copy of this work source without any package names. * If any {@link WorkChain WorkChains} are present, they are left intact. * * @return a {@link WorkSource} without any package names. * @hide */ @SystemApi @NonNull public WorkSource withoutNames() { final WorkSource copy = new WorkSource(this); copy.clearNames(); return copy; } /** * 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 @TestApi public WorkSource addReturningNewbs(WorkSource other) { synchronized (sTmpWorkSource) { sNewbWork = null; updateLocked(other, false, true); return sNewbWork; } } /** @hide */ @UnsupportedAppUsage @TestApi public boolean add(int uid) { if (mNum <= 0) { mNames = null; insert(0, uid); return true; } if (mNames != null) { throw new IllegalArgumentException("Adding without name to named " + this); } int i = Arrays.binarySearch(mUids, 0, mNum, uid); if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i); if (i >= 0) { return false; } insert(-i-1, uid); return true; } /** @hide */ @UnsupportedAppUsage @TestApi public boolean add(int uid, String name) { if (mNum <= 0) { insert(0, uid, name); return true; } if (mNames == null) { throw new IllegalArgumentException("Adding name to unnamed " + this); } int i; for (i=0; i uid) { break; } if (mUids[i] == uid) { int diff = mNames[i].compareTo(name); if (diff > 0) { break; } if (diff == 0) { return false; } } } insert(i, uid, name); return true; } public boolean remove(WorkSource other) { if (isEmpty() || other.isEmpty()) { return false; } boolean uidRemoved; if (mNames == null && other.mNames == null) { uidRemoved = removeUids(other); } else { if (mNames == null) { throw new IllegalArgumentException("Other " + other + " has names, but target " + this + " does not"); } if (other.mNames == null) { throw new IllegalArgumentException("Target " + this + " has names, but other " + other + " does not"); } uidRemoved = removeUidsAndNames(other); } boolean chainRemoved = false; if (other.mChains != null && mChains != null) { chainRemoved = mChains.removeAll(other.mChains); } return uidRemoved || chainRemoved; } /** * Create a new {@code WorkChain} associated with this WorkSource and return it. * * @hide */ @SystemApi public WorkChain createWorkChain() { if (mChains == null) { mChains = new ArrayList<>(4); } final WorkChain wc = new WorkChain(); mChains.add(wc); return wc; } /** * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to * attribute usage to. * * @hide for internal use only. */ @SystemApi public boolean isEmpty() { return mNum == 0 && (mChains == null || mChains.isEmpty()); } /** * @return the list of {@code WorkChains} associated with this {@code WorkSource}. * @hide */ @SystemApi @Nullable public List getWorkChains() { return mChains; } /** * DO NOT USE: Hacky API provided solely for {@code GnssLocationProvider}. See * {@code setReturningDiffs} as well. * * @hide */ public void transferWorkChains(WorkSource other) { if (mChains != null) { mChains.clear(); } if (other.mChains == null || other.mChains.isEmpty()) { return; } if (mChains == null) { mChains = new ArrayList<>(4); } mChains.addAll(other.mChains); other.mChains.clear(); } private boolean removeUids(WorkSource other) { int N1 = mNum; final int[] uids1 = mUids; final int N2 = other.mNum; final int[] uids2 = other.mUids; boolean changed = false; int i1 = 0, i2 = 0; if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this); while (i1 < N1 && i2 < N2) { if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2 + " of " + N2); if (uids2[i2] == uids1[i1]) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": remove " + uids1[i1]); N1--; changed = true; if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1); i2++; } else if (uids2[i2] > uids1[i1]) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1"); i1++; } else { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2"); i2++; } } mNum = N1; return changed; } private boolean removeUidsAndNames(WorkSource other) { int N1 = mNum; final int[] uids1 = mUids; final String[] names1 = mNames; final int N2 = other.mNum; final int[] uids2 = other.mUids; final String[] names2 = other.mNames; boolean changed = false; int i1 = 0, i2 = 0; if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this); while (i1 < N1 && i2 < N2) { if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2 + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]); if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": remove " + uids1[i1] + " " + names1[i1]); N1--; changed = true; if (i1 < N1) { System.arraycopy(uids1, i1+1, uids1, i1, N1-i1); System.arraycopy(names1, i1+1, names1, i1, N1-i1); } i2++; } else if (uids2[i2] > uids1[i1] || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1"); i1++; } else { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2"); i2++; } } mNum = N1; return changed; } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) { if (mNames == null && other.mNames == null) { return updateUidsLocked(other, set, returnNewbs); } else { if (mNum > 0 && mNames == null) { throw new IllegalArgumentException("Other " + other + " has names, but target " + this + " does not"); } if (other.mNum > 0 && other.mNames == null) { throw new IllegalArgumentException("Target " + this + " has names, but other " + other + " does not"); } return updateUidsAndNamesLocked(other, set, returnNewbs); } } private static WorkSource addWork(WorkSource cur, int newUid) { if (cur == null) { return new WorkSource(newUid); } cur.insert(cur.mNum, newUid); return cur; } private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) { int N1 = mNum; int[] uids1 = mUids; final int N2 = other.mNum; final int[] uids2 = other.mUids; boolean changed = false; int i1 = 0, i2 = 0; if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set + " returnNewbs=" + returnNewbs); while (i1 < N1 || i2 < N2) { if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2 + " of " + N2); if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) { // Need to insert a new uid. if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": insert " + uids2[i2]); changed = true; if (uids1 == null) { uids1 = new int[4]; uids1[0] = uids2[i2]; } else if (N1 >= uids1.length) { int[] newuids = new int[(uids1.length*3)/2]; if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1); if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1); uids1 = newuids; uids1[i1] = uids2[i2]; } else { if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1); uids1[i1] = uids2[i2]; } if (returnNewbs) { sNewbWork = addWork(sNewbWork, uids2[i2]); } N1++; i1++; i2++; } else { if (!set) { // Skip uids that already exist or are not in 'other'. if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip"); if (i2 < N2 && uids2[i2] == uids1[i1]) { i2++; } i1++; } else { // Remove any uids that don't exist in 'other'. int start = i1; while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]); sGoneWork = addWork(sGoneWork, uids1[i1]); i1++; } if (start < i1) { System.arraycopy(uids1, i1, uids1, start, N1-i1); N1 -= i1-start; i1 = start; } // If there is a matching uid, skip it. if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip"); i1++; i2++; } } } } mNum = N1; mUids = uids1; return changed; } /** * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'. */ private int compare(WorkSource other, int i1, int i2) { final int diff = mUids[i1] - other.mUids[i2]; if (diff != 0) { return diff; } return mNames[i1].compareTo(other.mNames[i2]); } private static WorkSource addWork(WorkSource cur, int newUid, String newName) { if (cur == null) { return new WorkSource(newUid, newName); } cur.insert(cur.mNum, newUid, newName); return cur; } private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) { final int N2 = other.mNum; final int[] uids2 = other.mUids; String[] names2 = other.mNames; boolean changed = false; int i1 = 0, i2 = 0; if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set + " returnNewbs=" + returnNewbs); while (i1 < mNum || i2 < N2) { if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2 + " of " + N2); int diff = -1; if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) { // Need to insert a new uid. changed = true; if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": insert " + uids2[i2] + " " + names2[i2]); insert(i1, uids2[i2], names2[i2]); if (returnNewbs) { sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]); } i1++; i2++; } else { if (!set) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip"); if (i2 < N2 && diff == 0) { i2++; } i1++; } else { // Remove any uids that don't exist in 'other'. int start = i1; while (diff < 0) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1] + " " + mNames[i1]); sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]); i1++; if (i1 >= mNum) { break; } diff = i2 < N2 ? compare(other, i1, i2) : -1; } if (start < i1) { System.arraycopy(mUids, i1, mUids, start, mNum-i1); System.arraycopy(mNames, i1, mNames, start, mNum-i1); mNum -= i1-start; i1 = start; } // If there is a matching uid, skip it. if (i1 < mNum && diff == 0) { if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip"); i1++; i2++; } } } } return changed; } private void insert(int index, int uid) { if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid); if (mUids == null) { mUids = new int[4]; mUids[0] = uid; mNum = 1; } else if (mNum >= mUids.length) { int[] newuids = new int[(mNum*3)/2]; if (index > 0) { System.arraycopy(mUids, 0, newuids, 0, index); } if (index < mNum) { System.arraycopy(mUids, index, newuids, index+1, mNum-index); } mUids = newuids; mUids[index] = uid; mNum++; } else { if (index < mNum) { System.arraycopy(mUids, index, mUids, index+1, mNum-index); } mUids[index] = uid; mNum++; } } private void insert(int index, int uid, String name) { if (mUids == null) { mUids = new int[4]; mUids[0] = uid; mNames = new String[4]; mNames[0] = name; mNum = 1; } else if (mNum >= mUids.length) { int[] newuids = new int[(mNum*3)/2]; String[] newnames = new String[(mNum*3)/2]; if (index > 0) { System.arraycopy(mUids, 0, newuids, 0, index); System.arraycopy(mNames, 0, newnames, 0, index); } if (index < mNum) { System.arraycopy(mUids, index, newuids, index+1, mNum-index); System.arraycopy(mNames, index, newnames, index+1, mNum-index); } mUids = newuids; mNames = newnames; mUids[index] = uid; mNames[index] = name; mNum++; } else { if (index < mNum) { System.arraycopy(mUids, index, mUids, index+1, mNum-index); System.arraycopy(mNames, index, mNames, index+1, mNum-index); } mUids[index] = uid; mNames[index] = name; mNum++; } } /** * 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): *

     * WorkChain {
     *   mUids = { 2456 }
     *   mTags = { null }
     *   mSize = 1;
     * }
     * 
* * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678: * *
     * WorkChain {
     *   mUids = { 5678, 2456 }
     *   mTags = { null, "c1" }
     *   mSize = 1
     * }
     * 
* * 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 */ @SystemApi public static final 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]; } /** @hide */ @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; } /** * Return the UID to which this WorkChain should be attributed to, i.e, the UID that * initiated the work and not the UID performing it. Returns -1 if the chain is empty. */ public int getAttributionUid() { return mSize > 0 ? mUids[0] : -1; } /** * Return the tag associated with the attribution UID. See (@link #getAttributionUid}. * Returns null if the chain is empty. */ public String getAttributionTag() { return mTags.length > 0 ? mTags[0] : null; } // 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. /** @hide */ @VisibleForTesting public int[] getUids() { int[] uids = new int[mSize]; System.arraycopy(mUids, 0, uids, 0, mSize); return uids; } /** @hide */ @VisibleForTesting public String[] getTags() { String[] tags = new String[mSize]; System.arraycopy(mTags, 0, tags, 0, mSize); return tags; } /** @hide */ @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; } @NonNull @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(@Nullable 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 @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public WorkChain createFromParcel(Parcel in) { return new WorkChain(in); } public WorkChain[] newArray(int size) { return new WorkChain[size]; } }; } /** * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}. * * Returns {@code null} if no differences exist, otherwise returns a two element array. The * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in * {@code oldWs} but not in {@code newWs}. * * @hide */ public static ArrayList[] diffChains(WorkSource oldWs, WorkSource newWs) { ArrayList newChains = null; ArrayList goneChains = null; // TODO(narayan): This is a naive O(M*N) algorithm that determines what has changed across // WorkSource objects. We can replace this with something smarter, for e.g by defining // a Comparator between WorkChains. It's unclear whether that will be more efficient if // the number of chains associated with a WorkSource is expected to be small if (oldWs.mChains != null) { for (int i = 0; i < oldWs.mChains.size(); ++i) { final WorkChain wc = oldWs.mChains.get(i); if (newWs.mChains == null || !newWs.mChains.contains(wc)) { if (goneChains == null) { goneChains = new ArrayList<>(oldWs.mChains.size()); } goneChains.add(wc); } } } if (newWs.mChains != null) { for (int i = 0; i < newWs.mChains.size(); ++i) { final WorkChain wc = newWs.mChains.get(i); if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) { if (newChains == null) { newChains = new ArrayList<>(newWs.mChains.size()); } newChains.add(wc); } } } if (newChains != null || goneChains != null) { return new ArrayList[] { newChains, goneChains }; } return null; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { 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 public String toString() { StringBuilder result = new StringBuilder(); result.append("WorkSource{"); for (int i = 0; i < mNum; i++) { if (i != 0) { result.append(", "); } result.append(mUids[i]); if (mNames != null) { result.append(" "); 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(); } /** @hide */ public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long workSourceToken = proto.start(fieldId); for (int i = 0; i < mNum; i++) { final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS); proto.write(WorkSourceProto.WorkSourceContentProto.UID, mUids[i]); if (mNames != null) { proto.write(WorkSourceProto.WorkSourceContentProto.NAME, mNames[i]); } proto.end(contentProto); } if (mChains != null) { for (int i = 0; i < mChains.size(); i++) { final WorkChain wc = mChains.get(i); final long workChain = proto.start(WorkSourceProto.WORK_CHAINS); final String[] tags = wc.getTags(); final int[] uids = wc.getUids(); for (int j = 0; j < tags.length; j++) { final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS); proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]); proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]); proto.end(contentProto); } proto.end(workChain); } } proto.end(workSourceToken); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public WorkSource createFromParcel(Parcel in) { return new WorkSource(in); } public WorkSource[] newArray(int size) { return new WorkSource[size]; } }; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy