Home | History | Annotate | Download | only in os
      1 package android.os;
      2 
      3 import android.annotation.Nullable;
      4 import android.annotation.SystemApi;
      5 import android.content.Context;
      6 import android.os.WorkSourceProto;
      7 import android.provider.Settings;
      8 import android.provider.Settings.Global;
      9 import android.util.Log;
     10 import android.util.proto.ProtoOutputStream;
     11 
     12 import com.android.internal.annotations.VisibleForTesting;
     13 
     14 import java.util.ArrayList;
     15 import java.util.Arrays;
     16 
     17 /**
     18  * Describes the source of some work that may be done by someone else.
     19  * Currently the public representation of what a work source is is not
     20  * defined; this is an opaque container.
     21  */
     22 public class WorkSource implements Parcelable {
     23     static final String TAG = "WorkSource";
     24     static final boolean DEBUG = false;
     25 
     26     int mNum;
     27     int[] mUids;
     28     String[] mNames;
     29 
     30     private ArrayList<WorkChain> mChains;
     31 
     32     /**
     33      * Internal statics to avoid object allocations in some operations.
     34      * The WorkSource object itself is not thread safe, but we need to
     35      * hold sTmpWorkSource lock while working with these statics.
     36      */
     37     static final WorkSource sTmpWorkSource = new WorkSource(0);
     38     /**
     39      * For returning newbie work from a modification operation.
     40      */
     41     static WorkSource sNewbWork;
     42     /**
     43      * For returning gone work form a modification operation.
     44      */
     45     static WorkSource sGoneWork;
     46 
     47     /**
     48      * Create an empty work source.
     49      */
     50     public WorkSource() {
     51         mNum = 0;
     52         mChains = null;
     53     }
     54 
     55     /**
     56      * Create a new WorkSource that is a copy of an existing one.
     57      * If <var>orig</var> is null, an empty WorkSource is created.
     58      */
     59     public WorkSource(WorkSource orig) {
     60         if (orig == null) {
     61             mNum = 0;
     62             mChains = null;
     63             return;
     64         }
     65         mNum = orig.mNum;
     66         if (orig.mUids != null) {
     67             mUids = orig.mUids.clone();
     68             mNames = orig.mNames != null ? orig.mNames.clone() : null;
     69         } else {
     70             mUids = null;
     71             mNames = null;
     72         }
     73 
     74         if (orig.mChains != null) {
     75             // Make a copy of all WorkChains that exist on |orig| since they are mutable.
     76             mChains = new ArrayList<>(orig.mChains.size());
     77             for (WorkChain chain : orig.mChains) {
     78                 mChains.add(new WorkChain(chain));
     79             }
     80         } else {
     81             mChains = null;
     82         }
     83     }
     84 
     85     /** @hide */
     86     public WorkSource(int uid) {
     87         mNum = 1;
     88         mUids = new int[] { uid, 0 };
     89         mNames = null;
     90         mChains = null;
     91     }
     92 
     93     /** @hide */
     94     public WorkSource(int uid, String name) {
     95         if (name == null) {
     96             throw new NullPointerException("Name can't be null");
     97         }
     98         mNum = 1;
     99         mUids = new int[] { uid, 0 };
    100         mNames = new String[] { name, null };
    101         mChains = null;
    102     }
    103 
    104     WorkSource(Parcel in) {
    105         mNum = in.readInt();
    106         mUids = in.createIntArray();
    107         mNames = in.createStringArray();
    108 
    109         int numChains = in.readInt();
    110         if (numChains > 0) {
    111             mChains = new ArrayList<>(numChains);
    112             in.readParcelableList(mChains, WorkChain.class.getClassLoader());
    113         } else {
    114             mChains = null;
    115         }
    116     }
    117 
    118     /**
    119      * Whether system services should create {@code WorkChains} (wherever possible) in the place
    120      * of flat UID lists.
    121      *
    122      * @hide
    123      */
    124     public static boolean isChainedBatteryAttributionEnabled(Context context) {
    125         return Settings.Global.getInt(context.getContentResolver(),
    126                 Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1;
    127     }
    128 
    129     /** @hide */
    130     public int size() {
    131         return mNum;
    132     }
    133 
    134     /** @hide */
    135     public int get(int index) {
    136         return mUids[index];
    137     }
    138 
    139     /** @hide */
    140     public String getName(int index) {
    141         return mNames != null ? mNames[index] : null;
    142     }
    143 
    144     /**
    145      * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
    146      * intact.
    147      *
    148      * <p>Useful when combining with another WorkSource that doesn't have names.
    149      * @hide
    150      */
    151     public void clearNames() {
    152         if (mNames != null) {
    153             mNames = null;
    154             // Clear out any duplicate uids now that we don't have names to disambiguate them.
    155             int destIndex = 1;
    156             int newNum = mNum;
    157             for (int sourceIndex = 1; sourceIndex < mNum; sourceIndex++) {
    158                 if (mUids[sourceIndex] == mUids[sourceIndex - 1]) {
    159                     newNum--;
    160                 } else {
    161                     mUids[destIndex] = mUids[sourceIndex];
    162                     destIndex++;
    163                 }
    164             }
    165             mNum = newNum;
    166         }
    167     }
    168 
    169     /**
    170      * Clear this WorkSource to be empty.
    171      */
    172     public void clear() {
    173         mNum = 0;
    174         if (mChains != null) {
    175             mChains.clear();
    176         }
    177     }
    178 
    179     @Override
    180     public boolean equals(Object o) {
    181         if (o instanceof WorkSource) {
    182             WorkSource other = (WorkSource) o;
    183 
    184             if (diff(other)) {
    185                 return false;
    186             }
    187 
    188             if (mChains != null && !mChains.isEmpty()) {
    189                 return mChains.equals(other.mChains);
    190             } else {
    191                 return other.mChains == null || other.mChains.isEmpty();
    192             }
    193         }
    194 
    195         return false;
    196     }
    197 
    198     @Override
    199     public int hashCode() {
    200         int result = 0;
    201         for (int i = 0; i < mNum; i++) {
    202             result = ((result << 4) | (result >>> 28)) ^ mUids[i];
    203         }
    204         if (mNames != null) {
    205             for (int i = 0; i < mNum; i++) {
    206                 result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
    207             }
    208         }
    209 
    210         if (mChains != null) {
    211             result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
    212         }
    213 
    214         return result;
    215     }
    216 
    217     /**
    218      * Compare this WorkSource with another.
    219      * @param other The WorkSource to compare against.
    220      * @return If there is a difference, true is returned.
    221      */
    222     // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
    223     // we keep its semantics the same and ignore any differences in WorkChains (if any).
    224     public boolean diff(WorkSource other) {
    225         int N = mNum;
    226         if (N != other.mNum) {
    227             return true;
    228         }
    229         final int[] uids1 = mUids;
    230         final int[] uids2 = other.mUids;
    231         final String[] names1 = mNames;
    232         final String[] names2 = other.mNames;
    233         for (int i=0; i<N; i++) {
    234             if (uids1[i] != uids2[i]) {
    235                 return true;
    236             }
    237             if (names1 != null && names2 != null && !names1[i].equals(names2[i])) {
    238                 return true;
    239             }
    240         }
    241         return false;
    242     }
    243 
    244     /**
    245      * Replace the current contents of this work source with the given
    246      * work source.  If {@code other} is null, the current work source
    247      * will be made empty.
    248      */
    249     public void set(WorkSource other) {
    250         if (other == null) {
    251             mNum = 0;
    252             if (mChains != null) {
    253                 mChains.clear();
    254             }
    255             return;
    256         }
    257         mNum = other.mNum;
    258         if (other.mUids != null) {
    259             if (mUids != null && mUids.length >= mNum) {
    260                 System.arraycopy(other.mUids, 0, mUids, 0, mNum);
    261             } else {
    262                 mUids = other.mUids.clone();
    263             }
    264             if (other.mNames != null) {
    265                 if (mNames != null && mNames.length >= mNum) {
    266                     System.arraycopy(other.mNames, 0, mNames, 0, mNum);
    267                 } else {
    268                     mNames = other.mNames.clone();
    269                 }
    270             } else {
    271                 mNames = null;
    272             }
    273         } else {
    274             mUids = null;
    275             mNames = null;
    276         }
    277 
    278         if (other.mChains != null) {
    279             if (mChains != null) {
    280                 mChains.clear();
    281             } else {
    282                 mChains = new ArrayList<>(other.mChains.size());
    283             }
    284 
    285             for (WorkChain chain : other.mChains) {
    286                 mChains.add(new WorkChain(chain));
    287             }
    288         }
    289     }
    290 
    291     /** @hide */
    292     public void set(int uid) {
    293         mNum = 1;
    294         if (mUids == null) mUids = new int[2];
    295         mUids[0] = uid;
    296         mNames = null;
    297         if (mChains != null) {
    298             mChains.clear();
    299         }
    300     }
    301 
    302     /** @hide */
    303     public void set(int uid, String name) {
    304         if (name == null) {
    305             throw new NullPointerException("Name can't be null");
    306         }
    307         mNum = 1;
    308         if (mUids == null) {
    309             mUids = new int[2];
    310             mNames = new String[2];
    311         }
    312         mUids[0] = uid;
    313         mNames[0] = name;
    314         if (mChains != null) {
    315             mChains.clear();
    316         }
    317     }
    318 
    319     /**
    320      * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
    321      * differences in chains are returned. This will be removed once its callers have been
    322      * rewritten.
    323      *
    324      * NOTE: This is currently only used in GnssLocationProvider.
    325      *
    326      * @hide
    327      * @deprecated for internal use only. WorkSources are opaque and no external callers should need
    328      *     to be aware of internal differences.
    329      */
    330     @Deprecated
    331     public WorkSource[] setReturningDiffs(WorkSource other) {
    332         synchronized (sTmpWorkSource) {
    333             sNewbWork = null;
    334             sGoneWork = null;
    335             updateLocked(other, true, true);
    336             if (sNewbWork != null || sGoneWork != null) {
    337                 WorkSource[] diffs = new WorkSource[2];
    338                 diffs[0] = sNewbWork;
    339                 diffs[1] = sGoneWork;
    340                 return diffs;
    341             }
    342             return null;
    343         }
    344     }
    345 
    346     /**
    347      * Merge the contents of <var>other</var> WorkSource in to this one.
    348      *
    349      * @param other The other WorkSource whose contents are to be merged.
    350      * @return Returns true if any new sources were added.
    351      */
    352     public boolean add(WorkSource other) {
    353         synchronized (sTmpWorkSource) {
    354             boolean uidAdded = updateLocked(other, false, false);
    355 
    356             boolean chainAdded = false;
    357             if (other.mChains != null) {
    358                 // NOTE: This is quite an expensive operation, especially if the number of chains
    359                 // is large. We could look into optimizing it if it proves problematic.
    360                 if (mChains == null) {
    361                     mChains = new ArrayList<>(other.mChains.size());
    362                 }
    363 
    364                 for (WorkChain wc : other.mChains) {
    365                     if (!mChains.contains(wc)) {
    366                         mChains.add(new WorkChain(wc));
    367                     }
    368                 }
    369             }
    370 
    371             return uidAdded || chainAdded;
    372         }
    373     }
    374 
    375     /**
    376      * Legacy API: DO NOT USE. Only in use from unit tests.
    377      *
    378      * @hide
    379      * @deprecated meant for unit testing use only. Will be removed in a future API revision.
    380      */
    381     @Deprecated
    382     public WorkSource addReturningNewbs(WorkSource other) {
    383         synchronized (sTmpWorkSource) {
    384             sNewbWork = null;
    385             updateLocked(other, false, true);
    386             return sNewbWork;
    387         }
    388     }
    389 
    390     /** @hide */
    391     public boolean add(int uid) {
    392         if (mNum <= 0) {
    393             mNames = null;
    394             insert(0, uid);
    395             return true;
    396         }
    397         if (mNames != null) {
    398             throw new IllegalArgumentException("Adding without name to named " + this);
    399         }
    400         int i = Arrays.binarySearch(mUids, 0, mNum, uid);
    401         if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i);
    402         if (i >= 0) {
    403             return false;
    404         }
    405         insert(-i-1, uid);
    406         return true;
    407     }
    408 
    409     /** @hide */
    410     public boolean add(int uid, String name) {
    411         if (mNum <= 0) {
    412             insert(0, uid, name);
    413             return true;
    414         }
    415         if (mNames == null) {
    416             throw new IllegalArgumentException("Adding name to unnamed " + this);
    417         }
    418         int i;
    419         for (i=0; i<mNum; i++) {
    420             if (mUids[i] > uid) {
    421                 break;
    422             }
    423             if (mUids[i] == uid) {
    424                 int diff = mNames[i].compareTo(name);
    425                 if (diff > 0) {
    426                     break;
    427                 }
    428                 if (diff == 0) {
    429                     return false;
    430                 }
    431             }
    432         }
    433         insert(i, uid, name);
    434         return true;
    435     }
    436 
    437     public boolean remove(WorkSource other) {
    438         if (isEmpty() || other.isEmpty()) {
    439             return false;
    440         }
    441 
    442         boolean uidRemoved;
    443         if (mNames == null && other.mNames == null) {
    444             uidRemoved = removeUids(other);
    445         } else {
    446             if (mNames == null) {
    447                 throw new IllegalArgumentException("Other " + other + " has names, but target "
    448                         + this + " does not");
    449             }
    450             if (other.mNames == null) {
    451                 throw new IllegalArgumentException("Target " + this + " has names, but other "
    452                         + other + " does not");
    453             }
    454             uidRemoved = removeUidsAndNames(other);
    455         }
    456 
    457         boolean chainRemoved = false;
    458         if (other.mChains != null && mChains != null) {
    459             chainRemoved = mChains.removeAll(other.mChains);
    460         }
    461 
    462         return uidRemoved || chainRemoved;
    463     }
    464 
    465     /**
    466      * Create a new {@code WorkChain} associated with this WorkSource and return it.
    467      *
    468      * @hide
    469      */
    470     @SystemApi
    471     public WorkChain createWorkChain() {
    472         if (mChains == null) {
    473             mChains = new ArrayList<>(4);
    474         }
    475 
    476         final WorkChain wc = new WorkChain();
    477         mChains.add(wc);
    478 
    479         return wc;
    480     }
    481 
    482     /**
    483      * Returns {@code true} iff. this work source contains zero UIDs and zero WorkChains to
    484      * attribute usage to.
    485      *
    486      * @hide for internal use only.
    487      */
    488     public boolean isEmpty() {
    489         return mNum == 0 && (mChains == null || mChains.isEmpty());
    490     }
    491 
    492     /**
    493      * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
    494      * @hide
    495      */
    496     public ArrayList<WorkChain> getWorkChains() {
    497         return mChains;
    498     }
    499 
    500     /**
    501      * DO NOT USE: Hacky API provided solely for {@code GnssLocationProvider}. See
    502      * {@code setReturningDiffs} as well.
    503      *
    504      * @hide
    505      */
    506     public void transferWorkChains(WorkSource other) {
    507         if (mChains != null) {
    508             mChains.clear();
    509         }
    510 
    511         if (other.mChains == null || other.mChains.isEmpty()) {
    512             return;
    513         }
    514 
    515         if (mChains == null) {
    516             mChains = new ArrayList<>(4);
    517         }
    518 
    519         mChains.addAll(other.mChains);
    520         other.mChains.clear();
    521     }
    522 
    523     private boolean removeUids(WorkSource other) {
    524         int N1 = mNum;
    525         final int[] uids1 = mUids;
    526         final int N2 = other.mNum;
    527         final int[] uids2 = other.mUids;
    528         boolean changed = false;
    529         int i1 = 0, i2 = 0;
    530         if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
    531         while (i1 < N1 && i2 < N2) {
    532             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
    533                     + " of " + N2);
    534             if (uids2[i2] == uids1[i1]) {
    535                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
    536                         + ": remove " + uids1[i1]);
    537                 N1--;
    538                 changed = true;
    539                 if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
    540                 i2++;
    541             } else if (uids2[i2] > uids1[i1]) {
    542                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
    543                 i1++;
    544             } else {
    545                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
    546                 i2++;
    547             }
    548         }
    549 
    550         mNum = N1;
    551 
    552         return changed;
    553     }
    554 
    555     private boolean removeUidsAndNames(WorkSource other) {
    556         int N1 = mNum;
    557         final int[] uids1 = mUids;
    558         final String[] names1 = mNames;
    559         final int N2 = other.mNum;
    560         final int[] uids2 = other.mUids;
    561         final String[] names2 = other.mNames;
    562         boolean changed = false;
    563         int i1 = 0, i2 = 0;
    564         if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
    565         while (i1 < N1 && i2 < N2) {
    566             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
    567                     + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]);
    568             if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) {
    569                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
    570                         + ": remove " + uids1[i1] + " " + names1[i1]);
    571                 N1--;
    572                 changed = true;
    573                 if (i1 < N1) {
    574                     System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
    575                     System.arraycopy(names1, i1+1, names1, i1, N1-i1);
    576                 }
    577                 i2++;
    578             } else if (uids2[i2] > uids1[i1]
    579                     || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) {
    580                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
    581                 i1++;
    582             } else {
    583                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
    584                 i2++;
    585             }
    586         }
    587 
    588         mNum = N1;
    589 
    590         return changed;
    591     }
    592 
    593     private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
    594         if (mNames == null && other.mNames == null) {
    595             return updateUidsLocked(other, set, returnNewbs);
    596         } else {
    597             if (mNum > 0 && mNames == null) {
    598                 throw new IllegalArgumentException("Other " + other + " has names, but target "
    599                         + this + " does not");
    600             }
    601             if (other.mNum > 0 && other.mNames == null) {
    602                 throw new IllegalArgumentException("Target " + this + " has names, but other "
    603                         + other + " does not");
    604             }
    605             return updateUidsAndNamesLocked(other, set, returnNewbs);
    606         }
    607     }
    608 
    609     private static WorkSource addWork(WorkSource cur, int newUid) {
    610         if (cur == null) {
    611             return new WorkSource(newUid);
    612         }
    613         cur.insert(cur.mNum, newUid);
    614         return cur;
    615     }
    616 
    617     private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) {
    618         int N1 = mNum;
    619         int[] uids1 = mUids;
    620         final int N2 = other.mNum;
    621         final int[] uids2 = other.mUids;
    622         boolean changed = false;
    623         int i1 = 0, i2 = 0;
    624         if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
    625                 + " returnNewbs=" + returnNewbs);
    626         while (i1 < N1 || i2 < N2) {
    627             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
    628                     + " of " + N2);
    629             if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) {
    630                 // Need to insert a new uid.
    631                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
    632                         + ": insert " + uids2[i2]);
    633                 changed = true;
    634                 if (uids1 == null) {
    635                     uids1 = new int[4];
    636                     uids1[0] = uids2[i2];
    637                 } else if (N1 >= uids1.length) {
    638                     int[] newuids = new int[(uids1.length*3)/2];
    639                     if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
    640                     if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
    641                     uids1 = newuids;
    642                     uids1[i1] = uids2[i2];
    643                 } else {
    644                     if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1);
    645                     uids1[i1] = uids2[i2];
    646                 }
    647                 if (returnNewbs) {
    648                     sNewbWork = addWork(sNewbWork, uids2[i2]);
    649                 }
    650                 N1++;
    651                 i1++;
    652                 i2++;
    653             } else {
    654                 if (!set) {
    655                     // Skip uids that already exist or are not in 'other'.
    656                     if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
    657                     if (i2 < N2 && uids2[i2] == uids1[i1]) {
    658                         i2++;
    659                     }
    660                     i1++;
    661                 } else {
    662                     // Remove any uids that don't exist in 'other'.
    663                     int start = i1;
    664                     while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) {
    665                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]);
    666                         sGoneWork = addWork(sGoneWork, uids1[i1]);
    667                         i1++;
    668                     }
    669                     if (start < i1) {
    670                         System.arraycopy(uids1, i1, uids1, start, N1-i1);
    671                         N1 -= i1-start;
    672                         i1 = start;
    673                     }
    674                     // If there is a matching uid, skip it.
    675                     if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) {
    676                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
    677                         i1++;
    678                         i2++;
    679                     }
    680                 }
    681             }
    682         }
    683 
    684         mNum = N1;
    685         mUids = uids1;
    686 
    687         return changed;
    688     }
    689 
    690     /**
    691      * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'.
    692      */
    693     private int compare(WorkSource other, int i1, int i2) {
    694         final int diff = mUids[i1] - other.mUids[i2];
    695         if (diff != 0) {
    696             return diff;
    697         }
    698         return mNames[i1].compareTo(other.mNames[i2]);
    699     }
    700 
    701     private static WorkSource addWork(WorkSource cur, int newUid, String newName) {
    702         if (cur == null) {
    703             return new WorkSource(newUid, newName);
    704         }
    705         cur.insert(cur.mNum, newUid, newName);
    706         return cur;
    707     }
    708 
    709     private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) {
    710         final int N2 = other.mNum;
    711         final int[] uids2 = other.mUids;
    712         String[] names2 = other.mNames;
    713         boolean changed = false;
    714         int i1 = 0, i2 = 0;
    715         if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
    716                 + " returnNewbs=" + returnNewbs);
    717         while (i1 < mNum || i2 < N2) {
    718             if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2
    719                     + " of " + N2);
    720             int diff = -1;
    721             if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) {
    722                 // Need to insert a new uid.
    723                 changed = true;
    724                 if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum
    725                         + ": insert " + uids2[i2] + " " + names2[i2]);
    726                 insert(i1, uids2[i2], names2[i2]);
    727                 if (returnNewbs) {
    728                     sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]);
    729                 }
    730                 i1++;
    731                 i2++;
    732             } else {
    733                 if (!set) {
    734                     if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
    735                     if (i2 < N2 && diff == 0) {
    736                         i2++;
    737                     }
    738                     i1++;
    739                 } else {
    740                     // Remove any uids that don't exist in 'other'.
    741                     int start = i1;
    742                     while (diff < 0) {
    743                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1]
    744                                 + " " + mNames[i1]);
    745                         sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]);
    746                         i1++;
    747                         if (i1 >= mNum) {
    748                             break;
    749                         }
    750                         diff = i2 < N2 ? compare(other, i1, i2) : -1;
    751                     }
    752                     if (start < i1) {
    753                         System.arraycopy(mUids, i1, mUids, start, mNum-i1);
    754                         System.arraycopy(mNames, i1, mNames, start, mNum-i1);
    755                         mNum -= i1-start;
    756                         i1 = start;
    757                     }
    758                     // If there is a matching uid, skip it.
    759                     if (i1 < mNum && diff == 0) {
    760                         if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
    761                         i1++;
    762                         i2++;
    763                     }
    764                 }
    765             }
    766         }
    767 
    768         return changed;
    769     }
    770 
    771     private void insert(int index, int uid)  {
    772         if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid);
    773         if (mUids == null) {
    774             mUids = new int[4];
    775             mUids[0] = uid;
    776             mNum = 1;
    777         } else if (mNum >= mUids.length) {
    778             int[] newuids = new int[(mNum*3)/2];
    779             if (index > 0) {
    780                 System.arraycopy(mUids, 0, newuids, 0, index);
    781             }
    782             if (index < mNum) {
    783                 System.arraycopy(mUids, index, newuids, index+1, mNum-index);
    784             }
    785             mUids = newuids;
    786             mUids[index] = uid;
    787             mNum++;
    788         } else {
    789             if (index < mNum) {
    790                 System.arraycopy(mUids, index, mUids, index+1, mNum-index);
    791             }
    792             mUids[index] = uid;
    793             mNum++;
    794         }
    795     }
    796 
    797     private void insert(int index, int uid, String name)  {
    798         if (mUids == null) {
    799             mUids = new int[4];
    800             mUids[0] = uid;
    801             mNames = new String[4];
    802             mNames[0] = name;
    803             mNum = 1;
    804         } else if (mNum >= mUids.length) {
    805             int[] newuids = new int[(mNum*3)/2];
    806             String[] newnames = new String[(mNum*3)/2];
    807             if (index > 0) {
    808                 System.arraycopy(mUids, 0, newuids, 0, index);
    809                 System.arraycopy(mNames, 0, newnames, 0, index);
    810             }
    811             if (index < mNum) {
    812                 System.arraycopy(mUids, index, newuids, index+1, mNum-index);
    813                 System.arraycopy(mNames, index, newnames, index+1, mNum-index);
    814             }
    815             mUids = newuids;
    816             mNames = newnames;
    817             mUids[index] = uid;
    818             mNames[index] = name;
    819             mNum++;
    820         } else {
    821             if (index < mNum) {
    822                 System.arraycopy(mUids, index, mUids, index+1, mNum-index);
    823                 System.arraycopy(mNames, index, mNames, index+1, mNum-index);
    824             }
    825             mUids[index] = uid;
    826             mNames[index] = name;
    827             mNum++;
    828         }
    829     }
    830 
    831     /**
    832      * Represents an attribution chain for an item of work being performed. An attribution chain is
    833      * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
    834      * of the work, and the node at the highest index performs the work. Nodes at other indices
    835      * are intermediaries that facilitate the work. Examples :
    836      *
    837      * (1) Work being performed by uid=2456 (no chaining):
    838      * <pre>
    839      * WorkChain {
    840      *   mUids = { 2456 }
    841      *   mTags = { null }
    842      *   mSize = 1;
    843      * }
    844      * </pre>
    845      *
    846      * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
    847      *
    848      * <pre>
    849      * WorkChain {
    850      *   mUids = { 5678, 2456 }
    851      *   mTags = { null, "c1" }
    852      *   mSize = 1
    853      * }
    854      * </pre>
    855      *
    856      * Attribution chains are mutable, though the only operation that can be performed on them
    857      * is the addition of a new node at the end of the attribution chain to represent
    858      *
    859      * @hide
    860      */
    861     @SystemApi
    862     public static final class WorkChain implements Parcelable {
    863         private int mSize;
    864         private int[] mUids;
    865         private String[] mTags;
    866 
    867         // @VisibleForTesting
    868         public WorkChain() {
    869             mSize = 0;
    870             mUids = new int[4];
    871             mTags = new String[4];
    872         }
    873 
    874         /** @hide */
    875         @VisibleForTesting
    876         public WorkChain(WorkChain other) {
    877             mSize = other.mSize;
    878             mUids = other.mUids.clone();
    879             mTags = other.mTags.clone();
    880         }
    881 
    882         private WorkChain(Parcel in) {
    883             mSize = in.readInt();
    884             mUids = in.createIntArray();
    885             mTags = in.createStringArray();
    886         }
    887 
    888         /**
    889          * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
    890          * {@code WorkChain}.
    891          */
    892         public WorkChain addNode(int uid, @Nullable String tag) {
    893             if (mSize == mUids.length) {
    894                 resizeArrays();
    895             }
    896 
    897             mUids[mSize] = uid;
    898             mTags[mSize] = tag;
    899             mSize++;
    900 
    901             return this;
    902         }
    903 
    904         /**
    905          * Return the UID to which this WorkChain should be attributed to, i.e, the UID that
    906          * initiated the work and not the UID performing it.
    907          */
    908         public int getAttributionUid() {
    909             return mUids[0];
    910         }
    911 
    912         /**
    913          * Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
    914          */
    915         public String getAttributionTag() {
    916             return mTags[0];
    917         }
    918 
    919         // TODO: The following three trivial getters are purely for testing and will be removed
    920         // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
    921         // diffing it etc.
    922 
    923 
    924         /** @hide */
    925         @VisibleForTesting
    926         public int[] getUids() {
    927             int[] uids = new int[mSize];
    928             System.arraycopy(mUids, 0, uids, 0, mSize);
    929             return uids;
    930         }
    931 
    932         /** @hide */
    933         @VisibleForTesting
    934         public String[] getTags() {
    935             String[] tags = new String[mSize];
    936             System.arraycopy(mTags, 0, tags, 0, mSize);
    937             return tags;
    938         }
    939 
    940         /** @hide */
    941         @VisibleForTesting
    942         public int getSize() {
    943             return mSize;
    944         }
    945 
    946         private void resizeArrays() {
    947             final int newSize = mSize * 2;
    948             int[] uids = new int[newSize];
    949             String[] tags = new String[newSize];
    950 
    951             System.arraycopy(mUids, 0, uids, 0, mSize);
    952             System.arraycopy(mTags, 0, tags, 0, mSize);
    953 
    954             mUids = uids;
    955             mTags = tags;
    956         }
    957 
    958         @Override
    959         public String toString() {
    960             StringBuilder result = new StringBuilder("WorkChain{");
    961             for (int i = 0; i < mSize; ++i) {
    962                 if (i != 0) {
    963                     result.append(", ");
    964                 }
    965                 result.append("(");
    966                 result.append(mUids[i]);
    967                 if (mTags[i] != null) {
    968                     result.append(", ");
    969                     result.append(mTags[i]);
    970                 }
    971                 result.append(")");
    972             }
    973 
    974             result.append("}");
    975             return result.toString();
    976         }
    977 
    978         @Override
    979         public int hashCode() {
    980             return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
    981         }
    982 
    983         @Override
    984         public boolean equals(Object o) {
    985             if (o instanceof WorkChain) {
    986                 WorkChain other = (WorkChain) o;
    987 
    988                 return mSize == other.mSize
    989                     && Arrays.equals(mUids, other.mUids)
    990                     && Arrays.equals(mTags, other.mTags);
    991             }
    992 
    993             return false;
    994         }
    995 
    996         @Override
    997         public int describeContents() {
    998             return 0;
    999         }
   1000 
   1001         @Override
   1002         public void writeToParcel(Parcel dest, int flags) {
   1003             dest.writeInt(mSize);
   1004             dest.writeIntArray(mUids);
   1005             dest.writeStringArray(mTags);
   1006         }
   1007 
   1008         public static final Parcelable.Creator<WorkChain> CREATOR =
   1009                 new Parcelable.Creator<WorkChain>() {
   1010                     public WorkChain createFromParcel(Parcel in) {
   1011                         return new WorkChain(in);
   1012                     }
   1013                     public WorkChain[] newArray(int size) {
   1014                         return new WorkChain[size];
   1015                     }
   1016                 };
   1017     }
   1018 
   1019     /**
   1020      * Computes the differences in WorkChains contained between {@code oldWs} and {@code newWs}.
   1021      *
   1022      * Returns {@code null} if no differences exist, otherwise returns a two element array. The
   1023      * first element is a list of "new" chains, i.e WorkChains present in {@code newWs} but not in
   1024      * {@code oldWs}. The second element is a list of "gone" chains, i.e WorkChains present in
   1025      * {@code oldWs} but not in {@code newWs}.
   1026      *
   1027      * @hide
   1028      */
   1029     public static ArrayList<WorkChain>[] diffChains(WorkSource oldWs, WorkSource newWs) {
   1030         ArrayList<WorkChain> newChains = null;
   1031         ArrayList<WorkChain> goneChains = null;
   1032 
   1033         // TODO(narayan): This is a dumb O(M*N) algorithm that determines what has changed across
   1034         // WorkSource objects. We can replace this with something smarter, for e.g by defining
   1035         // a Comparator between WorkChains. It's unclear whether that will be more efficient if
   1036         // the number of chains associated with a WorkSource is expected to be small
   1037         if (oldWs.mChains != null) {
   1038             for (int i = 0; i < oldWs.mChains.size(); ++i) {
   1039                 final WorkChain wc = oldWs.mChains.get(i);
   1040                 if (newWs.mChains == null || !newWs.mChains.contains(wc)) {
   1041                     if (goneChains == null) {
   1042                         goneChains = new ArrayList<>(oldWs.mChains.size());
   1043                     }
   1044                     goneChains.add(wc);
   1045                 }
   1046             }
   1047         }
   1048 
   1049         if (newWs.mChains != null) {
   1050             for (int i = 0; i < newWs.mChains.size(); ++i) {
   1051                 final WorkChain wc = newWs.mChains.get(i);
   1052                 if (oldWs.mChains == null || !oldWs.mChains.contains(wc)) {
   1053                     if (newChains == null) {
   1054                         newChains = new ArrayList<>(newWs.mChains.size());
   1055                     }
   1056                     newChains.add(wc);
   1057                 }
   1058             }
   1059         }
   1060 
   1061         if (newChains != null || goneChains != null) {
   1062             return new ArrayList[] { newChains, goneChains };
   1063         }
   1064 
   1065         return null;
   1066     }
   1067 
   1068     @Override
   1069     public int describeContents() {
   1070         return 0;
   1071     }
   1072 
   1073     @Override
   1074     public void writeToParcel(Parcel dest, int flags) {
   1075         dest.writeInt(mNum);
   1076         dest.writeIntArray(mUids);
   1077         dest.writeStringArray(mNames);
   1078 
   1079         if (mChains == null) {
   1080             dest.writeInt(-1);
   1081         } else {
   1082             dest.writeInt(mChains.size());
   1083             dest.writeParcelableList(mChains, flags);
   1084         }
   1085     }
   1086 
   1087     @Override
   1088     public String toString() {
   1089         StringBuilder result = new StringBuilder();
   1090         result.append("WorkSource{");
   1091         for (int i = 0; i < mNum; i++) {
   1092             if (i != 0) {
   1093                 result.append(", ");
   1094             }
   1095             result.append(mUids[i]);
   1096             if (mNames != null) {
   1097                 result.append(" ");
   1098                 result.append(mNames[i]);
   1099             }
   1100         }
   1101 
   1102         if (mChains != null) {
   1103             result.append(" chains=");
   1104             for (int i = 0; i < mChains.size(); ++i) {
   1105                 if (i != 0) {
   1106                     result.append(", ");
   1107                 }
   1108                 result.append(mChains.get(i));
   1109             }
   1110         }
   1111 
   1112         result.append("}");
   1113         return result.toString();
   1114     }
   1115 
   1116     /** @hide */
   1117     public void writeToProto(ProtoOutputStream proto, long fieldId) {
   1118         final long workSourceToken = proto.start(fieldId);
   1119         for (int i = 0; i < mNum; i++) {
   1120             final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
   1121             proto.write(WorkSourceProto.WorkSourceContentProto.UID, mUids[i]);
   1122             if (mNames != null) {
   1123                 proto.write(WorkSourceProto.WorkSourceContentProto.NAME, mNames[i]);
   1124             }
   1125             proto.end(contentProto);
   1126         }
   1127 
   1128         if (mChains != null) {
   1129             for (int i = 0; i < mChains.size(); i++) {
   1130                 final WorkChain wc = mChains.get(i);
   1131                 final long workChain = proto.start(WorkSourceProto.WORK_CHAINS);
   1132 
   1133                 final String[] tags = wc.getTags();
   1134                 final int[] uids = wc.getUids();
   1135                 for (int j = 0; j < tags.length; j++) {
   1136                     final long contentProto = proto.start(WorkSourceProto.WORK_SOURCE_CONTENTS);
   1137                     proto.write(WorkSourceProto.WorkSourceContentProto.UID, uids[j]);
   1138                     proto.write(WorkSourceProto.WorkSourceContentProto.NAME, tags[j]);
   1139                     proto.end(contentProto);
   1140                 }
   1141 
   1142                 proto.end(workChain);
   1143             }
   1144         }
   1145 
   1146         proto.end(workSourceToken);
   1147     }
   1148 
   1149     public static final Parcelable.Creator<WorkSource> CREATOR
   1150             = new Parcelable.Creator<WorkSource>() {
   1151         public WorkSource createFromParcel(Parcel in) {
   1152             return new WorkSource(in);
   1153         }
   1154         public WorkSource[] newArray(int size) {
   1155             return new WorkSource[size];
   1156         }
   1157     };
   1158 }
   1159